diff --git a/.bazeliskversion b/.bazeliskversion index 6a126f402d53d..4dae2985b58cc 100644 --- a/.bazeliskversion +++ b/.bazeliskversion @@ -1 +1 @@ -1.7.5 +1.10.1 diff --git a/.bazelrc b/.bazelrc index 524542a02a4aa..91c4870ebd126 100644 --- a/.bazelrc +++ b/.bazelrc @@ -10,9 +10,6 @@ build --remote_timeout=30 build --remote_header=x-buildbuddy-api-key=3EYk49W2NefOx2n3yMze build --remote_accept_cached=true -# BuildBuddy -## Metadata settings -build --workspace_status_command="node ./src/dev/bazel_workspace_status.js" # Enable this in case you want to share your build info # build --build_metadata=VISIBILITY=PUBLIC build --build_metadata=TEST_GROUPS=//packages diff --git a/.bazelrc.common b/.bazelrc.common index 3de2bceaad3a6..c401a90507982 100644 --- a/.bazelrc.common +++ b/.bazelrc.common @@ -120,8 +120,8 @@ test --incompatible_strict_action_env # collect coverage information from test targets coverage --instrument_test_targets -# Settings for CI -# Bazel flags for CI are in /src/dev/ci_setup/.bazelrc-ci +# Metadata settings +build --workspace_status_command="node ./src/dev/bazel_workspace_status.js" # Load any settings specific to the current user. # .bazelrc.user should appear in .gitignore so that settings are not shared with team members diff --git a/.bazelversion b/.bazelversion index fcdb2e109f68c..fae6e3d04b2ca 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -4.0.0 +4.2.1 diff --git a/.buildkite/pipelines/es_snapshots/verify.yml b/.buildkite/pipelines/es_snapshots/verify.yml index b9aa0e0e3727a..9cddade0b7482 100755 --- a/.buildkite/pipelines/es_snapshots/verify.yml +++ b/.buildkite/pipelines/es_snapshots/verify.yml @@ -13,6 +13,7 @@ steps: - command: .buildkite/scripts/lifecycle/pre_build.sh label: Pre-Build + timeout_in_minutes: 10 - wait @@ -22,6 +23,7 @@ steps: queue: c2-16 key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" + timeout_in_minutes: 60 - command: .buildkite/scripts/steps/functional/xpack_cigroup.sh label: 'Default CI Group' @@ -81,6 +83,7 @@ steps: - command: .buildkite/scripts/steps/es_snapshots/trigger_promote.sh label: Trigger promotion + timeout_in_minutes: 10 depends_on: - default-cigroup - default-cigroup-docker @@ -93,3 +96,4 @@ steps: - command: .buildkite/scripts/lifecycle/post_build.sh label: Post-Build + timeout_in_minutes: 10 diff --git a/.buildkite/pipelines/hourly.yml b/.buildkite/pipelines/hourly.yml index 0edba11836fcd..3337cfb5dfcdd 100644 --- a/.buildkite/pipelines/hourly.yml +++ b/.buildkite/pipelines/hourly.yml @@ -3,6 +3,7 @@ env: steps: - command: .buildkite/scripts/lifecycle/pre_build.sh label: Pre-Build + timeout_in_minutes: 10 - wait @@ -12,6 +13,7 @@ steps: queue: c2-16 key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" + timeout_in_minutes: 60 - command: .buildkite/scripts/steps/functional/xpack_cigroup.sh label: 'Default CI Group' @@ -143,21 +145,25 @@ steps: agents: queue: n2-2 key: linting + timeout_in_minutes: 90 - command: .buildkite/scripts/steps/checks.sh label: 'Checks' agents: queue: c2-4 key: checks + timeout_in_minutes: 120 - command: .buildkite/scripts/steps/storybooks/build_and_upload.sh label: 'Build Storybooks' agents: queue: c2-4 key: storybooks + timeout_in_minutes: 60 - wait: ~ continue_on_failure: true - command: .buildkite/scripts/lifecycle/post_build.sh label: Post-Build + timeout_in_minutes: 10 diff --git a/.buildkite/pipelines/on_merge.yml b/.buildkite/pipelines/on_merge.yml index bceb1796479a2..2aba49bfa6460 100644 --- a/.buildkite/pipelines/on_merge.yml +++ b/.buildkite/pipelines/on_merge.yml @@ -4,21 +4,27 @@ env: steps: - command: .buildkite/scripts/lifecycle/pre_build.sh label: Pre-Build + timeout_in_minutes: 10 - wait - 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 - command: .buildkite/scripts/steps/on_merge_ts_refs_api_docs.sh label: Build TS Refs and Check Public API Docs agents: queue: c2-4 + timeout_in_minutes: 80 - wait: ~ continue_on_failure: true - command: .buildkite/scripts/lifecycle/post_build.sh label: Post-Build + timeout_in_minutes: 10 diff --git a/.buildkite/pipelines/pull_request/base.yml b/.buildkite/pipelines/pull_request/base.yml index 404bfb273b6f7..1013a841dfd27 100644 --- a/.buildkite/pipelines/pull_request/base.yml +++ b/.buildkite/pipelines/pull_request/base.yml @@ -1,6 +1,7 @@ steps: - command: .buildkite/scripts/lifecycle/pre_build.sh label: Pre-Build + timeout_in_minutes: 10 - wait @@ -10,6 +11,7 @@ steps: queue: c2-16 key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" + timeout_in_minutes: 60 - command: .buildkite/scripts/steps/functional/xpack_cigroup.sh label: 'Default CI Group' @@ -17,7 +19,7 @@ steps: agents: queue: ci-group-6 depends_on: build - timeout_in_minutes: 120 + timeout_in_minutes: 150 key: default-cigroup retry: automatic: @@ -141,15 +143,18 @@ steps: agents: queue: n2-2 key: linting + timeout_in_minutes: 90 - command: .buildkite/scripts/steps/checks.sh label: 'Checks' agents: queue: c2-4 key: checks + timeout_in_minutes: 120 - command: .buildkite/scripts/steps/storybooks/build_and_upload.sh label: 'Build Storybooks' agents: queue: c2-4 key: storybooks + timeout_in_minutes: 60 diff --git a/.buildkite/pipelines/update_demo_env.yml b/.buildkite/pipelines/update_demo_env.yml index 1c15b227a2e4a..e2dfdd782fd41 100644 --- a/.buildkite/pipelines/update_demo_env.yml +++ b/.buildkite/pipelines/update_demo_env.yml @@ -1,8 +1,10 @@ steps: - command: .buildkite/scripts/steps/demo_env/es_and_init.sh label: Initialize Environment and Deploy ES + timeout_in_minutes: 10 - command: .buildkite/scripts/steps/demo_env/kibana.sh label: Build and Deploy Kibana agents: queue: c2-8 + timeout_in_minutes: 60 diff --git a/.buildkite/scripts/bootstrap.sh b/.buildkite/scripts/bootstrap.sh index 3c6283a4fe3fd..df38c105d2fd3 100755 --- a/.buildkite/scripts/bootstrap.sh +++ b/.buildkite/scripts/bootstrap.sh @@ -3,6 +3,7 @@ set -euo pipefail source .buildkite/scripts/common/util.sh +source .buildkite/scripts/common/setup_bazel.sh echo "--- yarn install and bootstrap" retry 2 15 yarn kbn bootstrap diff --git a/.buildkite/scripts/common/persist_bazel_cache.sh b/.buildkite/scripts/common/persist_bazel_cache.sh deleted file mode 100755 index 357805c11acec..0000000000000 --- a/.buildkite/scripts/common/persist_bazel_cache.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -source .buildkite/scripts/common/util.sh - -KIBANA_BUILDBUDDY_CI_API_KEY=$(retry 5 5 vault read -field=value secret/kibana-issues/dev/kibana-buildbuddy-ci-api-key) -export KIBANA_BUILDBUDDY_CI_API_KEY - -# overwrites the file checkout .bazelrc file with the one intended for CI env -cp "$KIBANA_DIR/src/dev/ci_setup/.bazelrc-ci" "$KIBANA_DIR/.bazelrc" - -### -### append auth token to buildbuddy into "$KIBANA_DIR/.bazelrc"; -### -echo "# Appended by .buildkite/scripts/persist_bazel_cache.sh" >> "$KIBANA_DIR/.bazelrc" -echo "build --remote_header=x-buildbuddy-api-key=$KIBANA_BUILDBUDDY_CI_API_KEY" >> "$KIBANA_DIR/.bazelrc" diff --git a/.buildkite/scripts/common/setup_bazel.sh b/.buildkite/scripts/common/setup_bazel.sh new file mode 100755 index 0000000000000..bff44c7ba8dd3 --- /dev/null +++ b/.buildkite/scripts/common/setup_bazel.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +source .buildkite/scripts/common/util.sh + +KIBANA_BUILDBUDDY_CI_API_KEY=$(retry 5 5 vault read -field=value secret/kibana-issues/dev/kibana-buildbuddy-ci-api-key) +export KIBANA_BUILDBUDDY_CI_API_KEY + +echo "[bazel] writing .bazelrc" +cat < $KIBANA_DIR/.bazelrc + # Generated by .buildkite/scripts/common/setup_bazel.sh + + import %workspace%/.bazelrc.common + + build --build_metadata=ROLE=CI +EOF + +if [[ "${BAZEL_CACHE_MODE:-none}" == read* ]]; then + echo "[bazel] enabling caching" +cat <> $KIBANA_DIR/.bazelrc + build --bes_results_url=https://app.buildbuddy.io/invocation/ + build --bes_backend=grpcs://cloud.buildbuddy.io + build --remote_cache=grpcs://cloud.buildbuddy.io + build --remote_timeout=3600 + build --remote_header=x-buildbuddy-api-key=$KIBANA_BUILDBUDDY_CI_API_KEY +EOF +fi + +if [[ "${BAZEL_CACHE_MODE:-none}" == "read" ]]; then + echo "[bazel] cache set to read-only" +cat <> $KIBANA_DIR/.bazelrc + build --noremote_upload_local_results +EOF +fi + +if [[ "${BAZEL_CACHE_MODE:-none}" != @(read|read-write|none|) ]]; then + echo "invalid value for BAZEL_CACHE_MODE received ($BAZEL_CACHE_MODE), expected one of [read,read-write,none]" + exit 1 +fi diff --git a/.buildkite/scripts/common/util.sh b/.buildkite/scripts/common/util.sh index a884a147577c9..0e5fb1c40eb6f 100755 --- a/.buildkite/scripts/common/util.sh +++ b/.buildkite/scripts/common/util.sh @@ -18,7 +18,7 @@ verify_no_git_changes() { RED='\033[0;31m' C_RESET='\033[0m' # Reset color - GIT_CHANGES="$(git ls-files --modified)" + GIT_CHANGES="$(git ls-files --modified -- . ':!:.bazelrc')" if [ "$GIT_CHANGES" ]; then echo -e "\n${RED}ERROR: '$1' caused changes to the following files:${C_RESET}\n" echo -e "$GIT_CHANGES\n" diff --git a/.buildkite/scripts/steps/on_merge_build_and_metrics.sh b/.buildkite/scripts/steps/on_merge_build_and_metrics.sh index 315ba08f8719b..1f1e492f87bec 100755 --- a/.buildkite/scripts/steps/on_merge_build_and_metrics.sh +++ b/.buildkite/scripts/steps/on_merge_build_and_metrics.sh @@ -2,9 +2,6 @@ set -euo pipefail -# Write Bazel cache for Linux -.buildkite/scripts/common/persist_bazel_cache.sh - .buildkite/scripts/bootstrap.sh .buildkite/scripts/build_kibana.sh .buildkite/scripts/post_build_kibana.sh diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index 287b376037abe..d3c44eab2a526 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -28,6 +28,7 @@ check_rules_nodejs_version(minimum_version_string = "3.8.0") node_repositories( node_repositories = { "16.11.1-darwin_amd64": ("node-v16.11.1-darwin-x64.tar.gz", "node-v16.11.1-darwin-x64", "ba54b8ed504bd934d03eb860fefe991419b4209824280d4274f6a911588b5e45"), + "16.11.1-darwin_arm64": ("node-v16.11.1-darwin-arm64.tar.gz", "node-v16.11.1-darwin-arm64", "5e772e478390fab3001b7148a923e4f22fca50170000f18b28475337d3a97248"), "16.11.1-linux_arm64": ("node-v16.11.1-linux-arm64.tar.xz", "node-v16.11.1-linux-arm64", "083fc51f0ea26de9041aaf9821874651a9fd3b20d1cf57071ce6b523a0436f17"), "16.11.1-linux_s390x": ("node-v16.11.1-linux-s390x.tar.xz", "node-v16.11.1-linux-s390x", "855b5c83c2ccb05273d50bb04376335c68d47df57f3187cdebe1f22b972d2825"), "16.11.1-linux_amd64": ("node-v16.11.1-linux-x64.tar.xz", "node-v16.11.1-linux-x64", "493bcc9b660eff983a6de65a0f032eb2717f57207edf74c745bcb86e360310b3"), diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 73efed79324fe..01e7beae61ce8 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -211,6 +211,7 @@ readonly links: { clusterPrivileges: string; elasticsearchSettings: string; elasticsearchEnableSecurity: string; + elasticsearchEnableApiKeys: string; indicesPrivileges: string; kibanaTLS: string; kibanaPrivileges: string; diff --git a/docs/user/alerting/alerting-setup.asciidoc b/docs/user/alerting/alerting-setup.asciidoc index 3b9868178fa8d..9aae3a27cf373 100644 --- a/docs/user/alerting/alerting-setup.asciidoc +++ b/docs/user/alerting/alerting-setup.asciidoc @@ -17,8 +17,7 @@ If you are using an *on-premises* Elastic Stack deployment: If you are using an *on-premises* Elastic Stack deployment with <>: -* You must enable Transport Layer Security (TLS) for communication <>. {kib} alerting uses <> to secure background rule checks and actions, and API keys require {ref}/configuring-tls.html#tls-http[TLS on the HTTP interface]. A proxy will not suffice. -* If you have enabled TLS and are still unable to access Alerting, ensure that you have not {ref}/security-settings.html#api-key-service-settings[explicitly disabled API keys]. +* If you are unable to access Alerting, ensure that you have not {ref}/security-settings.html#api-key-service-settings[explicitly disabled API keys]. The Alerting framework uses queries that require the `search.allow_expensive_queries` setting to be `true`. See the scripts {ref}/query-dsl-script-query.html#_allow_expensive_queries_4[documentation]. diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 91ad185447986..87b05eeafc568 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -357,6 +357,7 @@ export class DocLinksService { clusterPrivileges: `${ELASTICSEARCH_DOCS}security-privileges.html#privileges-list-cluster`, elasticsearchSettings: `${ELASTICSEARCH_DOCS}security-settings.html`, elasticsearchEnableSecurity: `${ELASTICSEARCH_DOCS}configuring-stack-security.html`, + elasticsearchEnableApiKeys: `${ELASTICSEARCH_DOCS}security-settings.html#api-key-service-settings`, indicesPrivileges: `${ELASTICSEARCH_DOCS}security-privileges.html#privileges-list-indices`, kibanaTLS: `${ELASTICSEARCH_DOCS}security-basic-setup.html#encrypt-internode-communication`, kibanaPrivileges: `${KIBANA_DOCS}kibana-privileges.html`, @@ -715,6 +716,7 @@ export interface DocLinksStart { clusterPrivileges: string; elasticsearchSettings: string; elasticsearchEnableSecurity: string; + elasticsearchEnableApiKeys: string; indicesPrivileges: string; kibanaTLS: string; kibanaPrivileges: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 508299686b0d9..353e5aa4607e4 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -680,6 +680,7 @@ export interface DocLinksStart { clusterPrivileges: string; elasticsearchSettings: string; elasticsearchEnableSecurity: string; + elasticsearchEnableApiKeys: string; indicesPrivileges: string; kibanaTLS: string; kibanaPrivileges: string; diff --git a/src/dev/build/lib/config.test.ts b/src/dev/build/lib/config.test.ts index 8bc5eb70c9437..b2afe3337230d 100644 --- a/src/dev/build/lib/config.test.ts +++ b/src/dev/build/lib/config.test.ts @@ -107,6 +107,7 @@ describe('#getTargetPlatforms()', () => { .sort() ).toMatchInlineSnapshot(` Array [ + "darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", @@ -132,7 +133,7 @@ describe('#getNodePlatforms()', () => { .getTargetPlatforms() .map((p) => p.getNodeArch()) .sort() - ).toEqual(['darwin-x64', 'linux-arm64', 'linux-x64', 'win32-x64']); + ).toEqual(['darwin-arm64', 'darwin-x64', 'linux-arm64', 'linux-x64', 'win32-x64']); }); it('returns this platform and linux, when targetAllPlatforms = false', async () => { diff --git a/src/dev/build/lib/platform.test.ts b/src/dev/build/lib/platform.test.ts index 79b07cac09cfc..193579d1a35c1 100644 --- a/src/dev/build/lib/platform.test.ts +++ b/src/dev/build/lib/platform.test.ts @@ -31,6 +31,7 @@ describe('isWindows()', () => { expect(new Platform('win32', 'x64', 'foo').isWindows()).toBe(true); expect(new Platform('linux', 'x64', 'foo').isWindows()).toBe(false); expect(new Platform('darwin', 'x64', 'foo').isWindows()).toBe(false); + expect(new Platform('darwin', 'arm64', 'foo').isWindows()).toBe(false); }); }); @@ -39,6 +40,7 @@ describe('isLinux()', () => { expect(new Platform('win32', 'x64', 'foo').isLinux()).toBe(false); expect(new Platform('linux', 'x64', 'foo').isLinux()).toBe(true); expect(new Platform('darwin', 'x64', 'foo').isLinux()).toBe(false); + expect(new Platform('darwin', 'arm64', 'foo').isLinux()).toBe(false); }); }); @@ -47,5 +49,6 @@ describe('isMac()', () => { expect(new Platform('win32', 'x64', 'foo').isMac()).toBe(false); expect(new Platform('linux', 'x64', 'foo').isMac()).toBe(false); expect(new Platform('darwin', 'x64', 'foo').isMac()).toBe(true); + expect(new Platform('darwin', 'arm64', 'foo').isMac()).toBe(true); }); }); diff --git a/src/dev/build/lib/platform.ts b/src/dev/build/lib/platform.ts index 2df7801ffc10e..4c4ec271318d6 100644 --- a/src/dev/build/lib/platform.ts +++ b/src/dev/build/lib/platform.ts @@ -49,5 +49,6 @@ export const ALL_PLATFORMS = [ new Platform('linux', 'x64', 'linux-x86_64'), new Platform('linux', 'arm64', 'linux-aarch64'), new Platform('darwin', 'x64', 'darwin-x86_64'), + new Platform('darwin', 'arm64', 'darwin-aarch64'), new Platform('win32', 'x64', 'windows-x86_64'), ]; diff --git a/src/dev/build/tasks/install_chromium.js b/src/dev/build/tasks/install_chromium.js index 95e0df8984f9d..ad60019ea81a4 100644 --- a/src/dev/build/tasks/install_chromium.js +++ b/src/dev/build/tasks/install_chromium.js @@ -16,7 +16,11 @@ export const InstallChromium = { async run(config, log, build) { for (const platform of config.getNodePlatforms()) { - log.info(`Installing Chromium for ${platform.getName()}-${platform.getArchitecture()}`); + const target = `${platform.getName()}-${platform.getArchitecture()}`; + log.info(`Installing Chromium for ${target}`); + + // revert after https://github.com/elastic/kibana/issues/109949 + if (target === 'darwin-arm64') continue; const { binaryPath$ } = installBrowser( log, diff --git a/src/dev/build/tasks/nodejs/download_node_builds_task.test.ts b/src/dev/build/tasks/nodejs/download_node_builds_task.test.ts index ca43f78a40cfd..31374d2050971 100644 --- a/src/dev/build/tasks/nodejs/download_node_builds_task.test.ts +++ b/src/dev/build/tasks/nodejs/download_node_builds_task.test.ts @@ -98,6 +98,15 @@ it('downloads node builds for each platform', async () => { "url": "darwin:url", }, ], + Array [ + Object { + "destination": "darwin:downloadPath", + "log": , + "retries": 3, + "sha256": "darwin:sha256", + "url": "darwin:url", + }, + ], Array [ Object { "destination": "win32:downloadPath", diff --git a/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts b/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts index 37a017ed083d0..9f869b99c18ae 100644 --- a/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts +++ b/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts @@ -105,6 +105,13 @@ it('runs expected fs operations', async () => { "strip": 1, }, ], + Array [ + /.node_binaries//node-v-darwin-arm64.tar.gz, + /.node_binaries//darwin-arm64, + Object { + "strip": 1, + }, + ], ], } `); diff --git a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts index b097deb46f61c..c636db145694c 100644 --- a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts +++ b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts @@ -98,6 +98,7 @@ it('checks shasums for each downloaded node build', async () => { Object { "type": "return", "value": Object { + "darwin:darwin-arm64:downloadName": "valid shasum", "darwin:darwin-x64:downloadName": "valid shasum", "linux:linux-arm64:downloadName": "valid shasum", "linux:linux-x64:downloadName": "valid shasum", @@ -134,6 +135,14 @@ it('checks shasums for each downloaded node build', async () => { "name": "darwin", }, ], + Array [ + , + Platform { + "architecture": "arm64", + "buildName": "darwin-aarch64", + "name": "darwin", + }, + ], Array [ , Platform { @@ -165,6 +174,13 @@ it('checks shasums for each downloaded node build', async () => { "downloadPath": "darwin:darwin-x64:downloadPath", }, }, + Object { + "type": "return", + "value": Object { + "downloadName": "darwin:darwin-arm64:downloadName", + "downloadPath": "darwin:darwin-arm64:downloadPath", + }, + }, Object { "type": "return", "value": Object { @@ -190,6 +206,10 @@ it('checks shasums for each downloaded node build', async () => { "darwin:darwin-x64:downloadPath", "sha256", ], + Array [ + "darwin:darwin-arm64:downloadPath", + "sha256", + ], Array [ "win32:win32-x64:downloadPath", "sha256", @@ -212,6 +232,10 @@ it('checks shasums for each downloaded node build', async () => { "type": "return", "value": "valid shasum", }, + Object { + "type": "return", + "value": "valid shasum", + }, ], } `); diff --git a/src/dev/build/tasks/patch_native_modules_task.ts b/src/dev/build/tasks/patch_native_modules_task.ts index bb2b9cc96b677..37cb729053785 100644 --- a/src/dev/build/tasks/patch_native_modules_task.ts +++ b/src/dev/build/tasks/patch_native_modules_task.ts @@ -58,6 +58,10 @@ const packages: Package[] = [ url: 'https://storage.googleapis.com/kibana-ci-proxy-cache/node-re2/uhop/node-re2/releases/download/1.16.0/linux-arm64-93.gz', sha256: '7a786e0b75985e5aafdefa9af55cad8e85e69a3326f16d8c63d21d6b5b3bff1b', }, + 'darwin-arm64': { + url: 'https://storage.googleapis.com/kibana-ci-proxy-cache/node-re2/uhop/node-re2/releases/download/1.16.0/darwin-arm64-93.gz', + sha256: '28b540cdddf13578f1bd28a03e29ffdc26a7f00ec859c369987b8d51ec6357c8', + }, 'win32-x64': { url: 'https://github.com/uhop/node-re2/releases/download/1.16.0/win32-x64-93.gz', sha256: '37245ceb59a086b5e7e9de8746a3cdf148c383be9ae2580f92baea90d0d39947', diff --git a/src/dev/ci_setup/.bazelrc-ci b/src/dev/ci_setup/.bazelrc-ci index 9aee657f37bcb..a0a0c3de73405 100644 --- a/src/dev/ci_setup/.bazelrc-ci +++ b/src/dev/ci_setup/.bazelrc-ci @@ -1,15 +1,5 @@ -# Used in the on-merge job to persist the Bazel cache to BuildBuddy -# from: .buildkite/scripts/common/persist_bazel_cache.sh +# Generated by .buildkite/scripts/common/setup_bazel.sh import %workspace%/.bazelrc.common -# BuildBuddy settings -build --bes_results_url=https://app.buildbuddy.io/invocation/ -build --bes_backend=grpcs://cloud.buildbuddy.io -build --remote_cache=grpcs://cloud.buildbuddy.io -build --remote_timeout=3600 -# --remote_header=x-buildbuddy-api-key= # appended in CI script - -# Metadata settings build --build_metadata=ROLE=CI -build --workspace_status_command="node ./src/dev/bazel_workspace_status.js" diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx index b7ec5a1f0c286..0541e12cf8172 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx @@ -103,7 +103,8 @@ function wrapQueryBarTopRowInContext(testProps: any) { ); } -describe('QueryBarTopRowTopRow', () => { +// Failing: See https://github.com/elastic/kibana/issues/92528 +describe.skip('QueryBarTopRowTopRow', () => { const QUERY_INPUT_SELECTOR = 'QueryStringInputUI'; const TIMEPICKER_SELECTOR = 'EuiSuperDatePicker'; const TIMEPICKER_DURATION = '[data-shared-timefilter-duration]'; diff --git a/src/plugins/vis_types/timeseries/public/application/components/aggs/field_select.tsx b/src/plugins/vis_types/timeseries/public/application/components/aggs/field_select.tsx index 610b4a91cfd14..8e1880a7a14d8 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/aggs/field_select.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/aggs/field_select.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import { i18n } from '@kbn/i18n'; -import React, { ReactNode, useContext } from 'react'; +import React, { ReactNode } from 'react'; import { EuiComboBox, EuiComboBoxProps, @@ -20,8 +20,6 @@ import type { TimeseriesUIRestrictions } from '../../../../common/ui_restriction // @ts-ignore import { isFieldEnabled } from '../../lib/check_ui_restrictions'; -import { PanelModelContext } from '../../contexts/panel_model_context'; -import { USE_KIBANA_INDEXES_KEY } from '../../../../common/constants'; interface FieldSelectProps { label: string | ReactNode; @@ -64,7 +62,6 @@ export function FieldSelect({ uiRestrictions, 'data-test-subj': dataTestSubj = 'metricsIndexPatternFieldsSelect', }: FieldSelectProps) { - const panelModel = useContext(PanelModelContext); const htmlId = htmlIdGenerator(); let selectedOptions: Array> = []; @@ -119,18 +116,10 @@ export function FieldSelect({ } }); - let isInvalid; + const isInvalid = Boolean(value && fields[fieldsSelector] && !selectedOptions.length); - if (Boolean(panelModel?.[USE_KIBANA_INDEXES_KEY])) { - isInvalid = Boolean(value && fields[fieldsSelector] && !selectedOptions.length); - - if (value && !selectedOptions.length) { - selectedOptions = [{ label: value!, id: 'INVALID_FIELD' }]; - } - } else { - if (value && fields[fieldsSelector] && !selectedOptions.length) { - onChange([]); - } + if (value && !selectedOptions.length) { + selectedOptions = [{ label: value, id: 'INVALID_FIELD' }]; } return ( diff --git a/test/functional/apps/discover/_source_filters.ts b/test/functional/apps/discover/_source_filters.ts index 912ffd8d552b9..134e7cca923b9 100644 --- a/test/functional/apps/discover/_source_filters.ts +++ b/test/functional/apps/discover/_source_filters.ts @@ -14,39 +14,40 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); - const PageObjects = getPageObjects(['common', 'timePicker', 'discover']); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'timePicker', 'discover', 'header', 'settings']); - // FLAKY: https://github.com/elastic/kibana/issues/113130 - describe.skip('source filters', function describeIndexTests() { + describe('source filters', function () { before(async function () { - // delete .kibana index and update configDoc - await kibanaServer.uiSettings.replace({ - defaultIndex: 'logstash-*', - }); - await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/visualize.json'); - - // and load a set of makelogs data await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); - - await kibanaServer.uiSettings.update({ + await kibanaServer.uiSettings.replace({ + defaultIndex: 'logstash-*', 'discover:searchFieldsFromSource': false, }); + log.debug('management'); + await PageObjects.common.navigateToApp('settings'); + await PageObjects.settings.clickKibanaIndexPatterns(); + await PageObjects.settings.clickIndexPatternLogstash(); + await PageObjects.settings.addFieldFilter('referer'); + await PageObjects.settings.addFieldFilter('relatedContent*'); + log.debug('discover'); await PageObjects.common.navigateToApp('discover'); - await PageObjects.timePicker.setDefaultAbsoluteRange(); + await PageObjects.discover.waitUntilSearchingHasFinished(); - // After hiding the time picker, we need to wait for - // the refresh button to hide before clicking the share button - await PageObjects.common.sleep(1000); + await retry.try(async function () { + expect(await PageObjects.discover.getDocHeader()).to.have.string('Document'); + }); }); after(async () => { await kibanaServer.importExport.unload( 'test/functional/fixtures/kbn_archiver/visualize.json' ); + await kibanaServer.uiSettings.unset('defaultIndex'); }); it('should not get the field referer', async function () { diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index b929a78072868..633040221ed36 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -558,6 +558,20 @@ export class SettingsPageObject extends FtrService { } } + async addFieldFilter(name: string) { + await this.testSubjects.click('tab-sourceFilters'); + await this.find.setValue('.euiFieldText', name); + await this.find.clickByButtonText('Add'); + const table = await this.find.byClassName('euiTable'); + await this.retry.waitFor('field filter to be added', async () => { + const tableCells = await table.findAllByCssSelector('td'); + const fieldNames = await mapAsync(tableCells, async (cell) => { + return (await cell.getVisibleText()).trim(); + }); + return fieldNames.includes(name); + }); + } + public async confirmSave() { await this.testSubjects.setValue('saveModalConfirmText', 'change'); await this.testSubjects.click('confirmModalConfirmButton'); 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 0e6b7fff04451..229f06f2e47fa 100644 --- a/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts @@ -146,6 +146,7 @@ Object { id: '1', actionTypeId: '.server-log', }, + namespaces: ['default'], }, }, { @@ -154,6 +155,7 @@ Object { id: '2', actionTypeId: '.slack', }, + namespaces: ['default'], }, }, ], @@ -170,6 +172,8 @@ Object { "__server-log": 1, "__slack": 1, }, + "countEmailByService": Object {}, + "countNamespaces": 1, "countTotal": 2, } `); @@ -220,6 +224,7 @@ Object { id: '1', actionTypeId: '.server-log', }, + namespaces: ['default'], }, }, { @@ -228,13 +233,35 @@ Object { id: '2', actionTypeId: '.slack', }, + namespaces: ['default'], }, }, ], }, }) ); - const telemetry = await getInUseTotalCount(mockEsClient, 'test'); + const telemetry = await getInUseTotalCount(mockEsClient, 'test', undefined, [ + { + id: 'test', + actionTypeId: '.email', + name: 'test', + isPreconfigured: true, + config: { + tenantId: 'sdsd', + clientId: 'sdfsdf', + }, + secrets: { + clientSecret: 'sdfsdf', + }, + }, + { + id: 'anotherServerLog', + actionTypeId: '.server-log', + name: 'test', + isPreconfigured: true, + secrets: {}, + }, + ]); expect(mockEsClient.search).toHaveBeenCalledTimes(2); expect(telemetry).toMatchInlineSnapshot(` @@ -245,6 +272,8 @@ Object { "__server-log": 1, "__slack": 1, }, + "countEmailByService": Object {}, + "countNamespaces": 1, "countTotal": 4, } `); @@ -423,6 +452,114 @@ Object { id: '1', actionTypeId: '.server-log', }, + namespaces: ['default'], + }, + }, + { + _source: { + action: { + id: '2', + actionTypeId: '.slack', + }, + namespaces: ['default'], + }, + }, + { + _source: { + action: { + id: '3', + actionTypeId: '.email', + }, + namespaces: ['default'], + }, + }, + ], + }, + }) + ); + const telemetry = await getInUseTotalCount(mockEsClient, 'test', undefined, [ + { + id: 'anotherServerLog', + actionTypeId: '.server-log', + name: 'test', + isPreconfigured: true, + secrets: {}, + }, + ]); + + expect(mockEsClient.search).toHaveBeenCalledTimes(2); + expect(telemetry).toMatchInlineSnapshot(` +Object { + "countByAlertHistoryConnectorType": 1, + "countByType": Object { + "__email": 3, + "__index": 1, + "__server-log": 1, + "__slack": 1, + }, + "countEmailByService": Object { + "other": 3, + }, + "countNamespaces": 1, + "countTotal": 6, +} +`); + }); + + test('getInUseTotalCount() accounts for actions namespaces', async () => { + const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; + mockEsClient.search.mockReturnValueOnce( + // @ts-expect-error not full search response + elasticsearchClientMock.createSuccessTransportRequestPromise({ + aggregations: { + refs: { + actionRefIds: { + value: { + connectorIds: { + '1': 'action-0', + '123': 'action-1', + '456': 'action-2', + }, + total: 3, + }, + }, + }, + preconfigured_actions: { + preconfiguredActionRefIds: { + value: { + total: 3, + actionRefs: { + 'preconfigured:preconfigured-alert-history-es-index': { + actionRef: 'preconfigured:preconfigured-alert-history-es-index', + actionTypeId: '.index', + }, + 'preconfigured:cloud_email': { + actionRef: 'preconfigured:cloud_email', + actionTypeId: '.email', + }, + 'preconfigured:cloud_email2': { + actionRef: 'preconfigured:cloud_email2', + actionTypeId: '.email', + }, + }, + }, + }, + }, + }, + }) + ); + mockEsClient.search.mockReturnValueOnce( + // @ts-expect-error not full search response + elasticsearchClientMock.createSuccessTransportRequestPromise({ + hits: { + hits: [ + { + _source: { + action: { + id: '1', + actionTypeId: '.server-log', + }, + namespaces: ['test'], }, }, { @@ -431,6 +568,7 @@ Object { id: '2', actionTypeId: '.slack', }, + namespaces: ['default'], }, }, { @@ -439,6 +577,7 @@ Object { id: '3', actionTypeId: '.email', }, + namespaces: ['test2'], }, }, ], @@ -457,6 +596,10 @@ Object { "__server-log": 1, "__slack": 1, }, + "countEmailByService": Object { + "other": 1, + }, + "countNamespaces": 3, "countTotal": 6, } `); diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.ts index 4a3d0c70e535a..1cb6bf8bfc74c 100644 --- a/x-pack/plugins/actions/server/usage/actions_telemetry.ts +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; import { ElasticsearchClient } from 'kibana/server'; import { AlertHistoryEsIndexConnectorId } from '../../common'; import { ActionResult, PreConfiguredAction } from '../types'; @@ -81,11 +82,15 @@ export async function getTotalCount( export async function getInUseTotalCount( esClient: ElasticsearchClient, - kibanaIndex: string + kibanaIndex: string, + referenceType?: string, + preconfiguredActions?: PreConfiguredAction[] ): Promise<{ countTotal: number; countByType: Record; countByAlertHistoryConnectorType: number; + countEmailByService: Record; + countNamespaces: number; }> { const scriptedMetric = { scripted_metric: { @@ -160,6 +165,63 @@ export async function getInUseTotalCount( }, }; + const mustQuery = [ + { + bool: { + should: [ + { + nested: { + path: 'references', + query: { + bool: { + filter: { + bool: { + must: [ + { + term: { + 'references.type': 'action', + }, + }, + ], + }, + }, + }, + }, + }, + }, + { + nested: { + path: 'alert.actions', + query: { + bool: { + filter: { + bool: { + must: [ + { + prefix: { + 'alert.actions.actionRef': { + value: 'preconfigured:', + }, + }, + }, + ], + }, + }, + }, + }, + }, + }, + ], + }, + }, + ] as QueryDslQueryContainer[]; + + if (!!referenceType) { + mustQuery.push({ + term: { type: referenceType }, + }); + } + const { body: actionResults } = await esClient.search({ index: kibanaIndex, body: { @@ -172,54 +234,7 @@ export async function getInUseTotalCount( type: 'action_task_params', }, }, - must: { - bool: { - should: [ - { - nested: { - path: 'references', - query: { - bool: { - filter: { - bool: { - must: [ - { - term: { - 'references.type': 'action', - }, - }, - ], - }, - }, - }, - }, - }, - }, - { - nested: { - path: 'alert.actions', - query: { - bool: { - filter: { - bool: { - must: [ - { - prefix: { - 'alert.actions.actionRef': { - value: 'preconfigured:', - }, - }, - }, - ], - }, - }, - }, - }, - }, - }, - ], - }, - }, + must: mustQuery, }, }, }, @@ -250,13 +265,15 @@ export async function getInUseTotalCount( const preconfiguredActionsAggs = // @ts-expect-error aggegation type is not specified actionResults.aggregations.preconfigured_actions?.preconfiguredActionRefIds.value; + const { body: { hits: actions }, } = await esClient.search<{ action: ActionResult; + namespaces: string[]; }>({ index: kibanaIndex, - _source_includes: ['action'], + _source_includes: ['action', 'namespaces'], body: { query: { bool: { @@ -274,6 +291,7 @@ export async function getInUseTotalCount( }, }, }); + const countByActionTypeId = actions.hits.reduce( (actionTypeCount: Record, action) => { const actionSource = action._source!; @@ -286,6 +304,26 @@ export async function getInUseTotalCount( {} ); + const namespacesList = actions.hits.reduce((_namespaces: Set, action) => { + const namespaces = action._source?.namespaces ?? ['default']; + namespaces.forEach((namespace) => { + if (!_namespaces.has(namespace)) { + _namespaces.add(namespace); + } + }); + return _namespaces; + }, new Set()); + + const countEmailByService = actions.hits + .filter((action) => action._source!.action.actionTypeId === '.email') + .reduce((emailServiceCount: Record, action) => { + const service = (action._source!.action.config?.service ?? 'other') as string; + const currentCount = + emailServiceCount[service] !== undefined ? emailServiceCount[service] : 0; + emailServiceCount[service] = currentCount + 1; + return emailServiceCount; + }, {}); + let preconfiguredAlertHistoryConnectors = 0; const preconfiguredActionsRefs: Array<{ actionTypeId: string; @@ -298,15 +336,40 @@ export async function getInUseTotalCount( if (actionRef === `preconfigured:${AlertHistoryEsIndexConnectorId}`) { preconfiguredAlertHistoryConnectors++; } + if (preconfiguredActions && actionTypeId === '__email') { + const preconfiguredConnectorId = actionRef.split(':')[1]; + const service = (preconfiguredActions.find( + (preconfConnector) => preconfConnector.id === preconfiguredConnectorId + )?.config?.service ?? 'other') as string; + const currentCount = + countEmailByService[service] !== undefined ? countEmailByService[service] : 0; + countEmailByService[service] = currentCount + 1; + } } return { countTotal: aggs.total + (preconfiguredActionsAggs?.total ?? 0), countByType: countByActionTypeId, countByAlertHistoryConnectorType: preconfiguredAlertHistoryConnectors, + countEmailByService, + countNamespaces: namespacesList.size, }; } +export async function getInUseByAlertingTotalCounts( + esClient: ElasticsearchClient, + kibanaIndex: string, + preconfiguredActions?: PreConfiguredAction[] +): Promise<{ + countTotal: number; + countByType: Record; + countByAlertHistoryConnectorType: number; + countEmailByService: Record; + countNamespaces: number; +}> { + return await getInUseTotalCount(esClient, kibanaIndex, 'alert', preconfiguredActions); +} + function replaceFirstAndLastDotSymbols(strToReplace: string) { const hasFirstSymbolDot = strToReplace.startsWith('.'); const appliedString = hasFirstSymbolDot ? strToReplace.replace('.', '__') : strToReplace; 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 80e0c19092c78..9ba9d7390a7b6 100644 --- a/x-pack/plugins/actions/server/usage/actions_usage_collector.ts +++ b/x-pack/plugins/actions/server/usage/actions_usage_collector.ts @@ -5,29 +5,12 @@ * 2.0. */ -import { MakeSchemaFrom, UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { get } from 'lodash'; import { TaskManagerStartContract } from '../../../task_manager/server'; -import { ActionsUsage } from './types'; +import { ActionsUsage, byServiceProviderTypeSchema, byTypeSchema } from './types'; import { ActionsConfig } from '../config'; -const byTypeSchema: MakeSchemaFrom['count_by_type'] = { - // 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' }, - // Known actions: - __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' }, -}; - export function createActionsUsageCollector( usageCollection: UsageCollectionSetup, config: ActionsConfig, @@ -45,6 +28,7 @@ export function createActionsUsageCollector( _meta: { description: 'Indicates if preconfigured alert history connector is enabled.' }, }, count_total: { type: 'long' }, + count_by_type: byTypeSchema, count_active_total: { type: 'long' }, count_active_alert_history_connectors: { type: 'long', @@ -52,8 +36,9 @@ export function createActionsUsageCollector( description: 'The total number of preconfigured alert history connectors used by rules.', }, }, - count_by_type: byTypeSchema, count_active_by_type: byTypeSchema, + count_active_email_connectors_by_service_type: byServiceProviderTypeSchema, + count_actions_namespaces: { type: 'long' }, }, fetch: async () => { try { @@ -69,10 +54,12 @@ export function createActionsUsageCollector( return { alert_history_connector_enabled: false, count_total: 0, + count_by_type: {}, count_active_total: 0, count_active_alert_history_connectors: 0, count_active_by_type: {}, - count_by_type: {}, + count_active_email_connectors_by_service_type: {}, + count_actions_namespaces: 0, }; } }, diff --git a/x-pack/plugins/actions/server/usage/task.ts b/x-pack/plugins/actions/server/usage/task.ts index 7cbfb87dedda6..bacb9e5f72571 100644 --- a/x-pack/plugins/actions/server/usage/task.ts +++ b/x-pack/plugins/actions/server/usage/task.ts @@ -83,7 +83,7 @@ export function telemetryTaskRunner( const esClient = await getEsClient(); return Promise.all([ getTotalCount(esClient, kibanaIndex, preconfiguredActions), - getInUseTotalCount(esClient, kibanaIndex), + getInUseTotalCount(esClient, kibanaIndex, undefined, preconfiguredActions), ]) .then(([totalAggegations, totalInUse]) => { return { @@ -94,6 +94,8 @@ export function telemetryTaskRunner( count_active_total: totalInUse.countTotal, count_active_by_type: totalInUse.countByType, count_active_alert_history_connectors: totalInUse.countByAlertHistoryConnectorType, + count_active_email_connectors_by_service_type: totalInUse.countEmailByService, + count_actions_namespaces: totalInUse.countNamespaces, }, runAt: getNextMidnight(), }; diff --git a/x-pack/plugins/actions/server/usage/types.ts b/x-pack/plugins/actions/server/usage/types.ts index 9221ba8ea5688..52677b35ac75b 100644 --- a/x-pack/plugins/actions/server/usage/types.ts +++ b/x-pack/plugins/actions/server/usage/types.ts @@ -5,14 +5,47 @@ * 2.0. */ +import { MakeSchemaFrom } from 'src/plugins/usage_collection/server'; + export interface ActionsUsage { alert_history_connector_enabled: boolean; count_total: number; + count_by_type: Record; count_active_total: number; count_active_alert_history_connectors: number; - count_by_type: Record; 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; } + +export const byTypeSchema: MakeSchemaFrom['count_by_type'] = { + // 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' }, + // Known actions: + __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' }, +}; + +export const byServiceProviderTypeSchema: MakeSchemaFrom['count_active_email_connectors_by_service_type'] = + { + DYNAMIC_KEY: { type: 'long' }, + // Known services: + exchange_server: { type: 'long' }, + gmail: { type: 'long' }, + outlook365: { type: 'long' }, + elastic_cloud: { type: 'long' }, + other: { type: 'long' }, + ses: { type: 'long' }, + }; 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 cce394d70ed6f..15fa6e63ac561 100644 --- a/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts +++ b/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts @@ -18,7 +18,14 @@ describe('alerts telemetry', () => { aggregations: { byAlertTypeId: { value: { - types: { '.index-threshold': 2, 'logs.alert.document.count': 1, 'document.test.': 1 }, + ruleTypes: { + '.index-threshold': 2, + 'logs.alert.document.count': 1, + 'document.test.': 1, + }, + namespaces: { + default: 1, + }, }, }, }, @@ -39,6 +46,7 @@ Object { "document.test__": 1, "logs.alert.document.count": 1, }, + "countNamespaces": 1, "countTotal": 4, } `); diff --git a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts b/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts index 7d8c1593f533d..18fa9b590b4e1 100644 --- a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts +++ b/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts @@ -10,10 +10,14 @@ import { AlertsUsage } from './types'; const alertTypeMetric = { scripted_metric: { - init_script: 'state.types = [:]', + init_script: 'state.ruleTypes = [:]; state.namespaces = [:]', map_script: ` String alertType = doc['alert.alertTypeId'].value; - state.types.put(alertType, state.types.containsKey(alertType) ? state.types.get(alertType) + 1 : 1); + String namespace = doc['namespaces'] !== null ? doc['namespaces'].value : 'default'; + state.ruleTypes.put(alertType, state.ruleTypes.containsKey(alertType) ? state.ruleTypes.get(alertType) + 1 : 1); + if (state.namespaces.containsKey(namespace) === false) { + state.namespaces.put(namespace, 1); + } `, // 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. @@ -40,7 +44,12 @@ export async function getTotalCountAggregations( ): Promise< Pick< AlertsUsage, - 'count_total' | 'count_by_type' | 'throttle_time' | 'schedule_time' | 'connectors_per_alert' + | 'count_total' + | 'count_by_type' + | 'throttle_time' + | 'schedule_time' + | 'connectors_per_alert' + | 'count_rules_namespaces' > > { const throttleTimeMetric = { @@ -247,7 +256,7 @@ export async function getTotalCountAggregations( }); const aggregations = results.aggregations as { - byAlertTypeId: { value: { types: Record } }; + byAlertTypeId: { value: { ruleTypes: Record } }; throttleTime: { value: { min: number; max: number; totalCount: number; totalSum: number } }; intervalTime: { value: { min: number; max: number; totalCount: number; totalSum: number } }; connectorsAgg: { @@ -257,20 +266,20 @@ export async function getTotalCountAggregations( }; }; - const totalAlertsCount = Object.keys(aggregations.byAlertTypeId.value.types).reduce( + const totalAlertsCount = Object.keys(aggregations.byAlertTypeId.value.ruleTypes).reduce( (total: number, key: string) => - parseInt(aggregations.byAlertTypeId.value.types[key], 10) + total, + parseInt(aggregations.byAlertTypeId.value.ruleTypes[key], 10) + total, 0 ); return { count_total: totalAlertsCount, - count_by_type: Object.keys(aggregations.byAlertTypeId.value.types).reduce( + count_by_type: Object.keys(aggregations.byAlertTypeId.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)]: aggregations.byAlertTypeId.value.types[key], + [replaceFirstAndLastDotSymbols(key)]: aggregations.byAlertTypeId.value.ruleTypes[key], }), {} ), @@ -300,6 +309,7 @@ export async function getTotalCountAggregations( : 0, max: aggregations.connectorsAgg.connectors.value.max, }, + count_rules_namespaces: 0, }; } @@ -319,24 +329,27 @@ export async function getTotalCountInUse(esClient: ElasticsearchClient, kibanaIn }); const aggregations = searchResult.aggregations as { - byAlertTypeId: { value: { types: Record } }; + byAlertTypeId: { + value: { ruleTypes: Record; namespaces: Record }; + }; }; return { - countTotal: Object.keys(aggregations.byAlertTypeId.value.types).reduce( + countTotal: Object.keys(aggregations.byAlertTypeId.value.ruleTypes).reduce( (total: number, key: string) => - parseInt(aggregations.byAlertTypeId.value.types[key], 10) + total, + parseInt(aggregations.byAlertTypeId.value.ruleTypes[key], 10) + total, 0 ), - countByType: Object.keys(aggregations.byAlertTypeId.value.types).reduce( + countByType: Object.keys(aggregations.byAlertTypeId.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)]: aggregations.byAlertTypeId.value.types[key], + [replaceFirstAndLastDotSymbols(key)]: aggregations.byAlertTypeId.value.ruleTypes[key], }), {} ), + countNamespaces: Object.keys(aggregations.byAlertTypeId.value.namespaces).length, }; } 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 453a29b5884e6..ecea721dfad92 100644 --- a/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts +++ b/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts @@ -91,6 +91,7 @@ export function createAlertsUsageCollector( }, count_active_by_type: {}, count_by_type: {}, + count_rules_namespaces: 0, }; } }, @@ -115,6 +116,7 @@ export function createAlertsUsageCollector( }, count_active_by_type: byTypeSchema, count_by_type: byTypeSchema, + count_rules_namespaces: { type: 'long' }, }, }); } diff --git a/x-pack/plugins/alerting/server/usage/task.ts b/x-pack/plugins/alerting/server/usage/task.ts index 043d970ddd231..9d39b3765cb5d 100644 --- a/x-pack/plugins/alerting/server/usage/task.ts +++ b/x-pack/plugins/alerting/server/usage/task.ts @@ -89,6 +89,7 @@ export function telemetryTaskRunner(logger: Logger, core: CoreSetup, kibanaIndex count_active_by_type: totalInUse.countByType, count_active_total: totalInUse.countTotal, count_disabled_total: totalCountAggregations.count_total - totalInUse.countTotal, + count_rules_namespaces: totalInUse.countNamespaces, }, runAt: getNextMidnight(), }; diff --git a/x-pack/plugins/alerting/server/usage/types.ts b/x-pack/plugins/alerting/server/usage/types.ts index c3c750da73a7f..5e420b54e37cb 100644 --- a/x-pack/plugins/alerting/server/usage/types.ts +++ b/x-pack/plugins/alerting/server/usage/types.ts @@ -11,6 +11,7 @@ export interface AlertsUsage { count_disabled_total: number; count_by_type: Record; count_active_by_type: Record; + count_rules_namespaces: number; throttle_time: { min: string; avg: string; diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/get_redirect_to_transaction_detail_page_url.test.ts b/x-pack/plugins/apm/public/components/app/TraceLink/get_redirect_to_transaction_detail_page_url.test.ts index 21b2f487fba91..56a4facd20496 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/get_redirect_to_transaction_detail_page_url.test.ts +++ b/x-pack/plugins/apm/public/components/app/TraceLink/get_redirect_to_transaction_detail_page_url.test.ts @@ -21,19 +21,35 @@ describe('getRedirectToTransactionDetailPageUrl', () => { }, } as unknown as any; - const url = getRedirectToTransactionDetailPageUrl({ transaction }); + describe('without time range', () => { + const url = getRedirectToTransactionDetailPageUrl({ transaction }); - it('rounds the start time down', () => { - expect(parse(url, true).query.rangeFrom).toBe('2020-01-01T00:00:00.000Z'); - }); + it('rounds the start time down', () => { + expect(parse(url, true).query.rangeFrom).toBe('2020-01-01T00:00:00.000Z'); + }); + + it('rounds the end time up', () => { + expect(parse(url, true).query.rangeTo).toBe('2020-01-01T00:05:00.000Z'); + }); - it('rounds the end time up', () => { - expect(parse(url, true).query.rangeTo).toBe('2020-01-01T00:05:00.000Z'); + it('formats url correctly', () => { + expect(url).toBe( + '/services/opbeans-node/transactions/view?traceId=trace_id&transactionId=transaction_id&transactionName=transaction_name&transactionType=request&rangeFrom=2020-01-01T00%3A00%3A00.000Z&rangeTo=2020-01-01T00%3A05%3A00.000Z' + ); + }); }); - it('formats url correctly', () => { - expect(url).toBe( - '/services/opbeans-node/transactions/view?traceId=trace_id&transactionId=transaction_id&transactionName=transaction_name&transactionType=request&rangeFrom=2020-01-01T00%3A00%3A00.000Z&rangeTo=2020-01-01T00%3A05%3A00.000Z' - ); + describe('with time range', () => { + const url = getRedirectToTransactionDetailPageUrl({ + transaction, + rangeFrom: '2020-01-01T00:02:00.000Z', + rangeTo: '2020-01-01T00:17:59.999Z', + }); + + it('uses timerange provided', () => { + expect(url).toBe( + '/services/opbeans-node/transactions/view?traceId=trace_id&transactionId=transaction_id&transactionName=transaction_name&transactionType=request&rangeFrom=2020-01-01T00%3A02%3A00.000Z&rangeTo=2020-01-01T00%3A17%3A59.999Z' + ); + }); }); }); diff --git a/x-pack/plugins/apm/public/components/app/correlations/correlations_table.tsx b/x-pack/plugins/apm/public/components/app/correlations/correlations_table.tsx index adf1805e0b9ae..eda3b64c309cc 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/correlations_table.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/correlations_table.tsx @@ -22,6 +22,7 @@ interface CorrelationsTableProps { significantTerms?: T[]; status: FETCH_STATUS; percentageColumnName?: string; + setPinnedSignificantTerm?: (term: T | null) => void; setSelectedSignificantTerm: (term: T | null) => void; selectedTerm?: FieldValuePair; onFilter?: () => void; @@ -33,6 +34,7 @@ interface CorrelationsTableProps { export function CorrelationsTable({ significantTerms, status, + setPinnedSignificantTerm, setSelectedSignificantTerm, columns, selectedTerm, @@ -91,6 +93,11 @@ export function CorrelationsTable({ columns={columns} rowProps={(term) => { return { + onClick: () => { + if (setPinnedSignificantTerm) { + setPinnedSignificantTerm(term); + } + }, onMouseEnter: () => { setSelectedSignificantTerm(term); trackSelectSignificantCorrelationTerm(); diff --git a/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx index a177733b3ecaf..838671cbae7d9 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx @@ -18,6 +18,7 @@ import { EuiTitle, EuiBetaBadge, EuiBadge, + EuiText, EuiToolTip, EuiSwitch, EuiIconTip, @@ -26,6 +27,8 @@ import type { EuiTableSortingType } from '@elastic/eui/src/components/basic_tabl import type { Direction } from '@elastic/eui/src/services/sort/sort_direction'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + import { enableInspectEsQueries, useUiTracker, @@ -37,10 +40,13 @@ import { APM_SEARCH_STRATEGIES, DEFAULT_PERCENTILE_THRESHOLD, } from '../../../../common/search_strategies/constants'; +import { FieldStats } from '../../../../common/search_strategies/field_stats_types'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { useLocalStorage } from '../../../hooks/useLocalStorage'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { useSearchStrategy } from '../../../hooks/use_search_strategy'; +import { useTheme } from '../../../hooks/use_theme'; import { ImpactBar } from '../../shared/ImpactBar'; import { push } from '../../shared/Links/url_helpers'; @@ -58,10 +64,8 @@ import { CorrelationsLog } from './correlations_log'; import { CorrelationsEmptyStatePrompt } from './empty_state_prompt'; import { CrossClusterSearchCompatibilityWarning } from './cross_cluster_search_warning'; import { CorrelationsProgressControls } from './progress_controls'; -import { useLocalStorage } from '../../../hooks/useLocalStorage'; -import { useTheme } from '../../../hooks/use_theme'; +import { useTransactionColors } from './use_transaction_colors'; import { CorrelationsContextPopover } from './context_popover'; -import { FieldStats } from '../../../../common/search_strategies/field_stats_types'; import { OnAddFilter } from './context_popover/top_values'; export function FailedTransactionsCorrelations({ @@ -69,6 +73,9 @@ export function FailedTransactionsCorrelations({ }: { onFilter: () => void; }) { + const euiTheme = useTheme(); + const transactionColors = useTransactionColors(); + const { core: { notifications, uiSettings }, } = useApmPluginContext(); @@ -96,18 +103,11 @@ export function FailedTransactionsCorrelations({ progress.isRunning ); - const [selectedSignificantTerm, setSelectedSignificantTerm] = - useState(null); - - const selectedTerm = - selectedSignificantTerm ?? response.failedTransactionsCorrelations?.[0]; - const history = useHistory(); const [showStats, setShowStats] = useLocalStorage( 'apmFailedTransactionsShowAdvancedStats', false ); - const euiTheme = useTheme(); const toggleShowStats = useCallback(() => { setShowStats(!showStats); @@ -410,6 +410,30 @@ export function FailedTransactionsCorrelations({ [response.failedTransactionsCorrelations, sortField, sortDirection] ); + const [pinnedSignificantTerm, setPinnedSignificantTerm] = + useState(null); + const [selectedSignificantTerm, setSelectedSignificantTerm] = + useState(null); + + const selectedTerm = useMemo(() => { + if (!correlationTerms) { + return; + } else if (selectedSignificantTerm) { + return correlationTerms?.find( + (h) => + h.fieldName === selectedSignificantTerm.fieldName && + h.fieldValue === selectedSignificantTerm.fieldValue + ); + } else if (pinnedSignificantTerm) { + return correlationTerms.find( + (h) => + h.fieldName === pinnedSignificantTerm.fieldName && + h.fieldValue === pinnedSignificantTerm.fieldValue + ); + } + return correlationTerms[0]; + }, [correlationTerms, pinnedSignificantTerm, selectedSignificantTerm]); + const showCorrelationsTable = progress.isRunning || correlationTerms.length > 0; @@ -497,6 +521,41 @@ export function FailedTransactionsCorrelations({ + {selectedTerm && ( + + , + allTransactions: ( + + + + ), + allFailedTransactions: ( + + + + ), + focusTransaction: ( + + {selectedTerm?.fieldName}:{selectedTerm?.fieldValue} + + ), + }} + /> + + )} + @@ -581,6 +645,7 @@ export function FailedTransactionsCorrelations({ status={ progress.isRunning ? FETCH_STATUS.LOADING : FETCH_STATUS.SUCCESS } + setPinnedSignificantTerm={setPinnedSignificantTerm} setSelectedSignificantTerm={setSelectedSignificantTerm} selectedTerm={selectedTerm} onTableChange={onTableChange} diff --git a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx index 75af7fae4ce12..db6f3ad63f00d 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx @@ -15,6 +15,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer, + EuiText, EuiTitle, EuiToolTip, } from '@elastic/eui'; @@ -22,6 +23,7 @@ import { Direction } from '@elastic/eui/src/services/sort/sort_direction'; import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { enableInspectEsQueries, @@ -34,6 +36,7 @@ import { DEFAULT_PERCENTILE_THRESHOLD, } from '../../../../common/search_strategies/constants'; import { LatencyCorrelation } from '../../../../common/search_strategies/latency_correlations/types'; +import { FieldStats } from '../../../../common/search_strategies/field_stats_types'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; @@ -53,11 +56,13 @@ import { CorrelationsLog } from './correlations_log'; import { CorrelationsEmptyStatePrompt } from './empty_state_prompt'; import { CrossClusterSearchCompatibilityWarning } from './cross_cluster_search_warning'; import { CorrelationsProgressControls } from './progress_controls'; -import { FieldStats } from '../../../../common/search_strategies/field_stats_types'; +import { useTransactionColors } from './use_transaction_colors'; import { CorrelationsContextPopover } from './context_popover'; import { OnAddFilter } from './context_popover/top_values'; export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { + const transactionColors = useTransactionColors(); + const { core: { notifications, uiSettings }, } = useApmPluginContext(); @@ -98,19 +103,11 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { } }, [progress.error, notifications.toasts]); + const [pinnedSignificantTerm, setPinnedSignificantTerm] = + useState(null); const [selectedSignificantTerm, setSelectedSignificantTerm] = useState(null); - const selectedHistogram = useMemo( - () => - response.latencyCorrelations?.find( - (h) => - h.fieldName === selectedSignificantTerm?.fieldName && - h.fieldValue === selectedSignificantTerm?.fieldValue - ) ?? response.latencyCorrelations?.[0], - [response.latencyCorrelations, selectedSignificantTerm] - ); - const history = useHistory(); const trackApmEvent = useUiTracker({ app: 'apm' }); @@ -270,6 +267,25 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { [response.latencyCorrelations, sortField, sortDirection] ); + const selectedHistogram = useMemo(() => { + if (!histogramTerms) { + return; + } else if (selectedSignificantTerm) { + return histogramTerms?.find( + (h) => + h.fieldName === selectedSignificantTerm.fieldName && + h.fieldValue === selectedSignificantTerm.fieldValue + ); + } else if (pinnedSignificantTerm) { + return histogramTerms.find( + (h) => + h.fieldName === pinnedSignificantTerm.fieldName && + h.fieldValue === pinnedSignificantTerm.fieldValue + ); + } + return histogramTerms[0]; + }, [histogramTerms, pinnedSignificantTerm, selectedSignificantTerm]); + const showCorrelationsTable = progress.isRunning || histogramTerms.length > 0; const showCorrelationsEmptyStatePrompt = histogramTerms.length < 1 && @@ -315,6 +331,31 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { + {selectedHistogram && ( + + , + allTransactions: ( + + + + ), + focusTransaction: ( + + {selectedHistogram?.fieldName}:{selectedHistogram?.fieldValue} + + ), + }} + /> + + )} + void }) { status={ progress.isRunning ? FETCH_STATUS.LOADING : FETCH_STATUS.SUCCESS } + setPinnedSignificantTerm={setPinnedSignificantTerm} setSelectedSignificantTerm={setSelectedSignificantTerm} selectedTerm={selectedHistogram} onTableChange={onTableChange} diff --git a/x-pack/plugins/apm/public/components/app/correlations/use_transaction_colors.ts b/x-pack/plugins/apm/public/components/app/correlations/use_transaction_colors.ts new file mode 100644 index 0000000000000..445640209cd29 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/correlations/use_transaction_colors.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 { useTheme } from '../../../hooks/use_theme'; + +export const useTransactionColors = () => { + const euiTheme = useTheme(); + return { + ALL_TRANSACTIONS: euiTheme.eui.euiColorVis1, + ALL_FAILED_TRANSACTIONS: euiTheme.eui.euiColorVis7, + FOCUS_TRANSACTION: euiTheme.eui.euiColorVis2, + }; +}; 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 109e52a84350f..8862596fd6d2a 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 @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect } from 'react'; +import React from 'react'; import { BrushEndListener, XYBrushEvent } from '@elastic/charts'; import { EuiBadge, @@ -16,30 +16,27 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; + import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { useUiTracker } from '../../../../../../observability/public'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; import { DEFAULT_PERCENTILE_THRESHOLD } from '../../../../../common/search_strategies/constants'; -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 { useApmParams } from '../../../../hooks/use_apm_params'; -import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; -import { useTimeRange } from '../../../../hooks/use_time_range'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; -import { - TransactionDistributionChart, - TransactionDistributionChartData, -} from '../../../shared/charts/transaction_distribution_chart'; -import { isErrorMessage } from '../../correlations/utils/is_error_message'; +import { TransactionDistributionChart } from '../../../shared/charts/transaction_distribution_chart'; +import { useTransactionColors } from '../../correlations/use_transaction_colors'; import type { TabContentProps } from '../types'; import { useWaterfallFetcher } from '../use_waterfall_fetcher'; import { WaterfallWithSummary } from '../waterfall_with_summary'; +import { useTransactionDistributionChartData } from './use_transaction_distribution_chart_data'; + // Enforce min height so it's consistent across all tabs on the same level // to prevent "flickering" behavior const MIN_TAB_TITLE_HEIGHT = 56; @@ -71,15 +68,8 @@ export function TransactionDistribution({ selection, traceSamples, }: TransactionDistributionProps) { - const { serviceName, transactionType } = useApmServiceContext(); - - const { - core: { notifications }, - } = useApmPluginContext(); - + const transactionColors = useTransactionColors(); const { urlParams } = useUrlParams(); - const { transactionName } = urlParams; - const { waterfall, status: waterfallStatus } = useWaterfallFetcher(); const markerCurrentTransaction = @@ -99,68 +89,6 @@ export function TransactionDistribution({ } ); - const { - query: { kuery, environment, rangeFrom, rangeTo }, - } = useApmParams('/services/{serviceName}/transactions/view'); - - const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - - const { - data = { log: [] }, - status, - error, - } = useFetcher( - (callApmApi) => { - if (serviceName && environment && start && end) { - return callApmApi({ - endpoint: 'GET /internal/apm/latency/overall_distribution', - params: { - query: { - serviceName, - transactionName, - transactionType, - kuery, - environment, - start, - end, - percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD, - }, - }, - }); - } - }, - [ - serviceName, - transactionName, - transactionType, - kuery, - environment, - start, - end, - ] - ); - - const overallHistogram = - data.overallHistogram === undefined && status !== FETCH_STATUS.LOADING - ? [] - : data.overallHistogram; - const hasData = - Array.isArray(overallHistogram) && overallHistogram.length > 0; - - useEffect(() => { - if (isErrorMessage(error)) { - notifications.toasts.addDanger({ - title: i18n.translate( - 'xpack.apm.transactionDetails.distribution.errorTitle', - { - defaultMessage: 'An error occurred fetching the distribution', - } - ), - text: error.toString(), - }); - } - }, [error, notifications.toasts]); - const trackApmEvent = useUiTracker({ app: 'apm' }); const onTrackedChartSelection = (brushEvent: XYBrushEvent) => { @@ -173,18 +101,8 @@ export function TransactionDistribution({ trackApmEvent({ metric: 'transaction_distribution_chart_clear_selection' }); }; - const transactionDistributionChartData: TransactionDistributionChartData[] = - []; - - if (Array.isArray(overallHistogram)) { - transactionDistributionChartData.push({ - id: i18n.translate( - 'xpack.apm.transactionDistribution.chart.allTransactionsLabel', - { defaultMessage: 'All transactions' } - ), - histogram: overallHistogram, - }); - } + const { chartData, hasData, percentileThresholdValue, status } = + useTransactionDistributionChartData(); return (
@@ -244,15 +162,46 @@ export function TransactionDistribution({ )} + + + + + ), + allFailedTransactions: ( + + + + ), + }} + /> + + 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 new file mode 100644 index 0000000000000..0edf5f648a980 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/use_transaction_distribution_chart_data.ts @@ -0,0 +1,187 @@ +/* + * Copyright 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 { useEffect, useMemo } from 'react'; + +import { i18n } from '@kbn/i18n'; + +import { DEFAULT_PERCENTILE_THRESHOLD } from '../../../../../common/search_strategies/constants'; +import { RawSearchStrategyClientParams } from '../../../../../common/search_strategies/types'; +import { EVENT_OUTCOME } from '../../../../../common/elasticsearch_fieldnames'; +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 { useApmParams } from '../../../../hooks/use_apm_params'; +import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; +import { useTimeRange } from '../../../../hooks/use_time_range'; + +import type { TransactionDistributionChartData } from '../../../shared/charts/transaction_distribution_chart'; + +import { isErrorMessage } from '../../correlations/utils/is_error_message'; + +function hasRequiredParams(params: RawSearchStrategyClientParams) { + const { serviceName, environment, start, end } = params; + return serviceName && environment && start && end; +} + +export const useTransactionDistributionChartData = () => { + const { serviceName, transactionType } = useApmServiceContext(); + + const { + core: { notifications }, + } = useApmPluginContext(); + + const { urlParams } = useUrlParams(); + const { transactionName } = urlParams; + + const { + query: { kuery, environment, rangeFrom, rangeTo }, + } = useApmParams('/services/{serviceName}/transactions/view'); + + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + + const params = useMemo( + () => ({ + serviceName, + transactionName, + transactionType, + kuery, + environment, + start, + end, + }), + [ + serviceName, + transactionName, + transactionType, + kuery, + environment, + start, + end, + ] + ); + + const { + // TODO The default object has `log: []` to retain compatibility with the shared search strategies code. + // Remove once the other tabs are migrated away from search strategies. + data: overallLatencyData = { log: [] }, + status: overallLatencyStatus, + error: overallLatencyError, + } = useFetcher( + (callApmApi) => { + if (hasRequiredParams(params)) { + return callApmApi({ + endpoint: 'POST /internal/apm/latency/overall_distribution', + params: { + body: { + ...params, + percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD, + }, + }, + }); + } + }, + [params] + ); + + useEffect(() => { + if (isErrorMessage(overallLatencyError)) { + notifications.toasts.addDanger({ + title: i18n.translate( + 'xpack.apm.transactionDetails.distribution.latencyDistributionErrorTitle', + { + defaultMessage: + 'An error occurred fetching the overall latency distribution.', + } + ), + text: overallLatencyError.toString(), + }); + } + }, [overallLatencyError, notifications.toasts]); + + const overallLatencyHistogram = + overallLatencyData.overallHistogram === undefined && + overallLatencyStatus !== FETCH_STATUS.LOADING + ? [] + : overallLatencyData.overallHistogram; + const hasData = + Array.isArray(overallLatencyHistogram) && + overallLatencyHistogram.length > 0; + + // TODO The default object has `log: []` to retain compatibility with the shared search strategies code. + // Remove once the other tabs are migrated away from search strategies. + const { data: errorHistogramData = { log: [] }, error: errorHistogramError } = + useFetcher( + (callApmApi) => { + if (hasRequiredParams(params)) { + return callApmApi({ + endpoint: 'POST /internal/apm/latency/overall_distribution', + params: { + body: { + ...params, + percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD, + termFilters: [ + { + fieldName: EVENT_OUTCOME, + fieldValue: EventOutcome.failure, + }, + ], + }, + }, + }); + } + }, + [params] + ); + + useEffect(() => { + if (isErrorMessage(errorHistogramError)) { + notifications.toasts.addDanger({ + title: i18n.translate( + 'xpack.apm.transactionDetails.distribution.failedTransactionsLatencyDistributionErrorTitle', + { + defaultMessage: + 'An error occurred fetching the failed transactions latency distribution.', + } + ), + text: errorHistogramError.toString(), + }); + } + }, [errorHistogramError, notifications.toasts]); + + const transactionDistributionChartData: TransactionDistributionChartData[] = + []; + + if (Array.isArray(overallLatencyHistogram)) { + transactionDistributionChartData.push({ + id: i18n.translate( + 'xpack.apm.transactionDistribution.chart.allTransactionsLabel', + { defaultMessage: 'All transactions' } + ), + histogram: overallLatencyHistogram, + }); + } + + if (Array.isArray(errorHistogramData.overallHistogram)) { + transactionDistributionChartData.push({ + id: i18n.translate( + 'xpack.apm.transactionDistribution.chart.allFailedTransactionsLabel', + { defaultMessage: 'All failed transactions' } + ), + histogram: errorHistogramData.overallHistogram, + }); + } + + return { + chartData: transactionDistributionChartData, + hasData, + percentileThresholdValue: overallLatencyData.percentileThresholdValue, + status: overallLatencyStatus, + }; +}; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/accordion_waterfall.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/accordion_waterfall.tsx index e4a851b890a7c..15883e7905142 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/accordion_waterfall.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/accordion_waterfall.tsx @@ -7,7 +7,7 @@ import { EuiAccordion, EuiAccordionProps } from '@elastic/eui'; import { isEmpty } from 'lodash'; -import React, { useState } from 'react'; +import React, { Dispatch, SetStateAction, useState } from 'react'; import { euiStyled } from '../../../../../../../../../../src/plugins/kibana_react/common'; import { Margins } from '../../../../../shared/charts/Timeline'; import { WaterfallItem } from './waterfall_item'; @@ -22,8 +22,8 @@ interface AccordionWaterfallProps { level: number; duration: IWaterfall['duration']; waterfallItemId?: string; + setMaxLevel: Dispatch>; waterfall: IWaterfall; - onToggleEntryTransaction?: () => void; timelineMargins: Margins; onClickWaterfallItem: (item: IWaterfallSpanOrTransaction) => void; } @@ -97,12 +97,13 @@ export function AccordionWaterfall(props: AccordionWaterfallProps) { duration, waterfall, waterfallItemId, + setMaxLevel, timelineMargins, onClickWaterfallItem, - onToggleEntryTransaction, } = props; const nextLevel = level + 1; + setMaxLevel(nextLevel); const children = waterfall.childrenByParentId[item.id] || []; const errorCount = waterfall.getErrorCount(item.id); @@ -139,9 +140,6 @@ export function AccordionWaterfall(props: AccordionWaterfallProps) { forceState={isOpen ? 'open' : 'closed'} onToggle={() => { setIsOpen((isCurrentOpen) => !isCurrentOpen); - if (onToggleEntryTransaction) { - onToggleEntryTransaction(); - } }} > {children.map((child) => ( diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/index.tsx index 3932a02c9d974..5b4bf99f7dae6 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/index.tsx @@ -29,13 +29,6 @@ const Container = euiStyled.div` overflow: hidden; `; -const TIMELINE_MARGINS = { - top: 40, - left: 100, - right: 50, - bottom: 0, -}; - const toggleFlyout = ({ history, item, @@ -72,6 +65,16 @@ export function Waterfall({ waterfall, waterfallItemId }: Props) { const agentMarks = getAgentMarks(waterfall.entryWaterfallTransaction?.doc); const errorMarks = getErrorMarks(waterfall.errorItems); + // Calculate the left margin relative to the deepest level, or 100px, whichever + // is more. + const [maxLevel, setMaxLevel] = useState(0); + const timelineMargins = { + top: 40, + left: Math.max(100, maxLevel * 10), + right: 50, + bottom: 0, + }; + return ( @@ -99,7 +102,7 @@ export function Waterfall({ waterfall, waterfallItemId }: Props) { marks={[...agentMarks, ...errorMarks]} xMax={duration} height={waterfallHeight} - margins={TIMELINE_MARGINS} + margins={timelineMargins} />
@@ -110,16 +113,14 @@ export function Waterfall({ waterfall, waterfallItemId }: Props) { isOpen={isAccordionOpen} item={waterfall.entryWaterfallTransaction} level={0} + setMaxLevel={setMaxLevel} waterfallItemId={waterfallItemId} duration={duration} waterfall={waterfall} - timelineMargins={TIMELINE_MARGINS} + timelineMargins={timelineMargins} onClickWaterfallItem={(item: IWaterfallItem) => toggleFlyout({ history, item }) } - onToggleEntryTransaction={() => - setIsAccordionOpen((isOpen) => !isOpen) - } /> )} diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_item.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_item.tsx index 4001a0624a809..caa0cac3acef8 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_item.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_item.tsx @@ -17,6 +17,7 @@ import { } from '../../../../../../../common/elasticsearch_fieldnames'; import { asDuration } from '../../../../../../../common/utils/formatters'; import { Margins } from '../../../../../shared/charts/Timeline'; +import { TruncateWithTooltip } from '../../../../../shared/truncate_with_tooltip'; import { SyncBadge } from './sync_badge'; import { IWaterfallSpanOrTransaction } from './waterfall_helpers/waterfall_helpers'; import { FailureBadge } from './failure_badge'; @@ -67,6 +68,7 @@ const ItemText = euiStyled.span` display: flex; align-items: center; height: ${({ theme }) => theme.eui.euiSizeL}; + max-width: 100%; /* add margin to all direct descendants */ & > * { @@ -160,7 +162,11 @@ function NameLabel({ item }: { item: IWaterfallSpanOrTransaction }) { : ''; name = `${item.doc.span.composite.count}${compositePrefix} ${name}`; } - return {name}; + return ( + + + + ); case 'transaction': return ( diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/WaterfallContainer.stories.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/WaterfallContainer.stories.tsx deleted file mode 100644 index a03b7b29f9666..0000000000000 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/WaterfallContainer.stories.tsx +++ /dev/null @@ -1,56 +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, { ComponentType } from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; -import { WaterfallContainer } from './index'; -import { getWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers'; -import { - inferredSpans, - simpleTrace, - traceChildStartBeforeParent, - traceWithErrors, - urlParams, -} from './waterfallContainer.stories.data'; - -export default { - title: 'app/TransactionDetails/Waterfall', - component: WaterfallContainer, - decorators: [ - (Story: ComponentType) => ( - - - - - - ), - ], -}; - -export function Example() { - const waterfall = getWaterfall(simpleTrace, '975c8d5bfd1dd20b'); - return ; -} - -export function WithErrors() { - const waterfall = getWaterfall(traceWithErrors, '975c8d5bfd1dd20b'); - return ; -} - -export function ChildStartsBeforeParent() { - const waterfall = getWaterfall( - traceChildStartBeforeParent, - '975c8d5bfd1dd20b' - ); - return ; -} - -export function InferredSpans() { - const waterfall = getWaterfall(inferredSpans, 'f2387d37260d00bd'); - return ; -} diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfallContainer.stories.data.ts b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfallContainer.stories.data.ts deleted file mode 100644 index 60285c835bbf3..0000000000000 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfallContainer.stories.data.ts +++ /dev/null @@ -1,2269 +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 { Location } from 'history'; -import type { ApmUrlParams } from '../../../../../context/url_params_context/types'; -import { APIReturnType } from '../../../../../services/rest/createCallApmApi'; - -export const location = { - pathname: '/services/opbeans-go/transactions/view', - search: - '?rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0&kuery=service.name%253A%2520%2522opbeans-java%2522%2520or%2520service.name%2520%253A%2520%2522opbeans-go%2522&traceId=513d33fafe99bbe6134749310c9b5322&transactionId=975c8d5bfd1dd20b&transactionName=GET%20%2Fapi%2Forders&transactionType=request', - hash: '', -} as Location; - -type TraceAPIResponse = APIReturnType<'GET /internal/apm/traces/{traceId}'>; - -export const urlParams = { - start: '2020-03-22T15:16:38.742Z', - end: '2020-03-23T15:16:38.742Z', - rangeFrom: 'now-24h', - rangeTo: 'now', - refreshPaused: true, - refreshInterval: 0, - page: 0, - transactionId: '975c8d5bfd1dd20b', - traceId: '513d33fafe99bbe6134749310c9b5322', - transactionName: 'GET /api/orders', - transactionType: 'request', - processorEvent: 'transaction', - serviceName: 'opbeans-go', -} as ApmUrlParams; - -export const simpleTrace = { - traceDocs: [ - { - container: { - id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', - }, - process: { - pid: 6, - title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java', - ppid: 1, - }, - agent: { - name: 'java', - ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb', - version: '1.14.1-SNAPSHOT', - }, - internal: { - sampler: { - value: 46, - }, - }, - source: { - ip: '172.19.0.13', - }, - processor: { - name: 'transaction', - event: 'transaction', - }, - url: { - path: '/api/orders', - scheme: 'http', - port: 3000, - domain: '172.19.0.9', - full: 'http://172.19.0.9:3000/api/orders', - }, - observer: { - hostname: 'f37f48d8b60b', - id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e', - ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9', - type: 'apm-server', - version: '8.0.0', - version_major: 8, - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T15:04:28.785Z', - ecs: { - version: '1.4.0', - }, - service: { - node: { - name: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', - }, - environment: 'production', - name: 'opbeans-java', - runtime: { - name: 'Java', - version: '10.0.2', - }, - language: { - name: 'Java', - version: '10.0.2', - }, - version: 'None', - }, - host: { - hostname: '4cf84d094553', - os: { - platform: 'Linux', - }, - ip: '172.19.0.9', - name: '4cf84d094553', - architecture: 'amd64', - }, - http: { - request: { - headers: { - Accept: ['*/*'], - 'User-Agent': ['Python/3.7 aiohttp/3.3.2'], - Host: ['172.19.0.9:3000'], - 'Accept-Encoding': ['gzip, deflate'], - }, - method: 'get', - socket: { - encrypted: false, - remote_address: '172.19.0.13', - }, - body: { - original: '[REDACTED]', - }, - }, - response: { - headers: { - 'Transfer-Encoding': ['chunked'], - Date: ['Mon, 23 Mar 2020 15:04:28 GMT'], - 'Content-Type': ['application/json;charset=ISO-8859-1'], - }, - status_code: 200, - finished: true, - headers_sent: true, - }, - version: '1.1', - }, - client: { - ip: '172.19.0.13', - }, - transaction: { - duration: { - us: 18842, - }, - result: 'HTTP 2xx', - name: 'DispatcherServlet#doGet', - id: '49809ad3c26adf74', - span_count: { - dropped: 0, - started: 1, - }, - type: 'request', - sampled: true, - }, - user_agent: { - original: 'Python/3.7 aiohttp/3.3.2', - name: 'Other', - device: { - name: 'Other', - }, - }, - timestamp: { - us: 1584975868785000, - }, - }, - { - parent: { - id: 'fc107f7b556eb49b', - }, - agent: { - name: 'go', - version: '1.7.2', - }, - processor: { - name: 'transaction', - event: 'transaction', - }, - url: { - path: '/api/orders', - scheme: 'http', - port: 3000, - domain: 'opbeans-go', - full: 'http://opbeans-go:3000/api/orders', - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T15:04:28.787Z', - service: { - node: { - name: 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29', - }, - environment: 'production', - framework: { - name: 'gin', - version: 'v1.4.0', - }, - name: 'opbeans-go', - runtime: { - name: 'gc', - version: 'go1.14.1', - }, - language: { - name: 'go', - version: 'go1.14.1', - }, - version: 'None', - }, - transaction: { - duration: { - us: 16597, - }, - result: 'HTTP 2xx', - name: 'GET /api/orders', - id: '975c8d5bfd1dd20b', - span_count: { - dropped: 0, - started: 1, - }, - type: 'request', - sampled: true, - }, - timestamp: { - us: 1584975868787052, - }, - }, - { - parent: { - id: 'daae24d83c269918', - }, - agent: { - name: 'python', - version: '5.5.2', - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - timestamp: { - us: 1584975868788603, - }, - processor: { - name: 'transaction', - event: 'transaction', - }, - url: { - path: '/api/orders', - scheme: 'http', - port: 3000, - domain: 'opbeans-go', - full: 'http://opbeans-go:3000/api/orders', - }, - '@timestamp': '2020-03-23T15:04:28.788Z', - service: { - node: { - name: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', - }, - environment: 'production', - framework: { - name: 'django', - version: '2.1.13', - }, - name: 'opbeans-python', - runtime: { - name: 'CPython', - version: '3.6.10', - }, - language: { - name: 'python', - version: '3.6.10', - }, - version: 'None', - }, - transaction: { - result: 'HTTP 2xx', - duration: { - us: 14648, - }, - name: 'GET opbeans.views.orders', - span_count: { - dropped: 0, - started: 1, - }, - id: '6fb0ff7365b87298', - type: 'request', - sampled: true, - }, - }, - { - container: { - id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', - }, - parent: { - id: '49809ad3c26adf74', - }, - process: { - pid: 6, - title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java', - ppid: 1, - }, - agent: { - name: 'java', - ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb', - version: '1.14.1-SNAPSHOT', - }, - internal: { - sampler: { - value: 44, - }, - }, - destination: { - address: 'opbeans-go', - port: 3000, - }, - processor: { - name: 'transaction', - event: 'span', - }, - observer: { - hostname: 'f37f48d8b60b', - id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e', - type: 'apm-server', - ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9', - version: '8.0.0', - version_major: 8, - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T15:04:28.785Z', - ecs: { - version: '1.4.0', - }, - service: { - node: { - name: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', - }, - environment: 'production', - name: 'opbeans-java', - runtime: { - name: 'Java', - version: '10.0.2', - }, - language: { - name: 'Java', - version: '10.0.2', - }, - version: 'None', - }, - host: { - hostname: '4cf84d094553', - os: { - platform: 'Linux', - }, - ip: '172.19.0.9', - name: '4cf84d094553', - architecture: 'amd64', - }, - connection: { - hash: "{service.environment:'production'}/{service.name:'opbeans-java'}/{span.subtype:'http'}/{destination.address:'opbeans-go'}/{span.type:'external'}", - }, - transaction: { - id: '49809ad3c26adf74', - }, - timestamp: { - us: 1584975868785273, - }, - span: { - duration: { - us: 17530, - }, - subtype: 'http', - name: 'GET opbeans-go', - destination: { - service: { - resource: 'opbeans-go:3000', - name: 'http://opbeans-go:3000', - type: 'external', - }, - }, - http: { - response: { - status_code: 200, - }, - url: { - original: 'http://opbeans-go:3000/api/orders', - }, - }, - id: 'fc107f7b556eb49b', - type: 'external', - }, - }, - { - parent: { - id: '975c8d5bfd1dd20b', - }, - agent: { - name: 'go', - version: '1.7.2', - }, - processor: { - name: 'transaction', - event: 'span', - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T15:04:28.787Z', - service: { - node: { - name: 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29', - }, - environment: 'production', - name: 'opbeans-go', - runtime: { - name: 'gc', - version: 'go1.14.1', - }, - language: { - name: 'go', - version: 'go1.14.1', - }, - version: 'None', - }, - transaction: { - id: '975c8d5bfd1dd20b', - }, - timestamp: { - us: 1584975868787174, - }, - span: { - duration: { - us: 16250, - }, - subtype: 'http', - destination: { - service: { - resource: 'opbeans-python:3000', - name: 'http://opbeans-python:3000', - type: 'external', - }, - }, - name: 'GET opbeans-python:3000', - http: { - response: { - status_code: 200, - }, - url: { - original: 'http://opbeans-python:3000/api/orders', - }, - }, - id: 'daae24d83c269918', - type: 'external', - }, - }, - { - container: { - id: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', - }, - parent: { - id: '6fb0ff7365b87298', - }, - agent: { - name: 'python', - version: '5.5.2', - }, - processor: { - name: 'transaction', - event: 'span', - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T15:04:28.790Z', - service: { - node: { - name: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', - }, - environment: 'production', - framework: { - name: 'django', - version: '2.1.13', - }, - name: 'opbeans-python', - runtime: { - name: 'CPython', - version: '3.6.10', - }, - language: { - name: 'python', - version: '3.6.10', - }, - version: 'None', - }, - transaction: { - id: '6fb0ff7365b87298', - }, - timestamp: { - us: 1584975868790080, - }, - span: { - duration: { - us: 2519, - }, - subtype: 'postgresql', - name: 'SELECT FROM opbeans_order', - destination: { - service: { - resource: 'postgresql', - name: 'postgresql', - type: 'db', - }, - }, - action: 'query', - id: 'c9407abb4d08ead1', - type: 'db', - sync: true, - db: { - statement: - 'SELECT "opbeans_order"."id", "opbeans_order"."customer_id", "opbeans_customer"."full_name", "opbeans_order"."created_at" FROM "opbeans_order" INNER JOIN "opbeans_customer" ON ("opbeans_order"."customer_id" = "opbeans_customer"."id") LIMIT 1000', - type: 'sql', - }, - }, - }, - ], - exceedsMax: false, - errorDocs: [], -} as TraceAPIResponse; - -export const traceWithErrors = { - traceDocs: [ - { - container: { - id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', - }, - process: { - pid: 6, - title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java', - ppid: 1, - }, - agent: { - name: 'java', - ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb', - version: '1.14.1-SNAPSHOT', - }, - internal: { - sampler: { - value: 46, - }, - }, - source: { - ip: '172.19.0.13', - }, - processor: { - name: 'transaction', - event: 'transaction', - }, - url: { - path: '/api/orders', - scheme: 'http', - port: 3000, - domain: '172.19.0.9', - full: 'http://172.19.0.9:3000/api/orders', - }, - observer: { - hostname: 'f37f48d8b60b', - id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e', - ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9', - type: 'apm-server', - version: '8.0.0', - version_major: 8, - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T15:04:28.785Z', - ecs: { - version: '1.4.0', - }, - service: { - node: { - name: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', - }, - environment: 'production', - name: 'opbeans-java', - runtime: { - name: 'Java', - version: '10.0.2', - }, - language: { - name: 'Java', - version: '10.0.2', - }, - version: 'None', - }, - host: { - hostname: '4cf84d094553', - os: { - platform: 'Linux', - }, - ip: '172.19.0.9', - name: '4cf84d094553', - architecture: 'amd64', - }, - http: { - request: { - headers: { - Accept: ['*/*'], - 'User-Agent': ['Python/3.7 aiohttp/3.3.2'], - Host: ['172.19.0.9:3000'], - 'Accept-Encoding': ['gzip, deflate'], - }, - method: 'get', - socket: { - encrypted: false, - remote_address: '172.19.0.13', - }, - body: { - original: '[REDACTED]', - }, - }, - response: { - headers: { - 'Transfer-Encoding': ['chunked'], - Date: ['Mon, 23 Mar 2020 15:04:28 GMT'], - 'Content-Type': ['application/json;charset=ISO-8859-1'], - }, - status_code: 200, - finished: true, - headers_sent: true, - }, - version: '1.1', - }, - client: { - ip: '172.19.0.13', - }, - transaction: { - duration: { - us: 18842, - }, - result: 'HTTP 2xx', - name: 'DispatcherServlet#doGet', - id: '49809ad3c26adf74', - span_count: { - dropped: 0, - started: 1, - }, - type: 'request', - sampled: true, - }, - user_agent: { - original: 'Python/3.7 aiohttp/3.3.2', - name: 'Other', - device: { - name: 'Other', - }, - }, - timestamp: { - us: 1584975868785000, - }, - }, - { - parent: { - id: 'fc107f7b556eb49b', - }, - agent: { - name: 'go', - version: '1.7.2', - }, - processor: { - name: 'transaction', - event: 'transaction', - }, - url: { - path: '/api/orders', - scheme: 'http', - port: 3000, - domain: 'opbeans-go', - full: 'http://opbeans-go:3000/api/orders', - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T15:04:28.787Z', - service: { - node: { - name: 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29', - }, - environment: 'production', - framework: { - name: 'gin', - version: 'v1.4.0', - }, - name: 'opbeans-go', - runtime: { - name: 'gc', - version: 'go1.14.1', - }, - language: { - name: 'go', - version: 'go1.14.1', - }, - version: 'None', - }, - transaction: { - duration: { - us: 16597, - }, - result: 'HTTP 2xx', - name: 'GET /api/orders', - id: '975c8d5bfd1dd20b', - span_count: { - dropped: 0, - started: 1, - }, - type: 'request', - sampled: true, - }, - timestamp: { - us: 1584975868787052, - }, - }, - { - parent: { - id: 'daae24d83c269918', - }, - agent: { - name: 'python', - version: '5.5.2', - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - timestamp: { - us: 1584975868788603, - }, - processor: { - name: 'transaction', - event: 'transaction', - }, - url: { - path: '/api/orders', - scheme: 'http', - port: 3000, - domain: 'opbeans-go', - full: 'http://opbeans-go:3000/api/orders', - }, - '@timestamp': '2020-03-23T15:04:28.788Z', - service: { - node: { - name: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', - }, - environment: 'production', - framework: { - name: 'django', - version: '2.1.13', - }, - name: 'opbeans-python', - runtime: { - name: 'CPython', - version: '3.6.10', - }, - language: { - name: 'python', - version: '3.6.10', - }, - version: 'None', - }, - transaction: { - result: 'HTTP 2xx', - duration: { - us: 14648, - }, - name: 'GET opbeans.views.orders', - span_count: { - dropped: 0, - started: 1, - }, - id: '6fb0ff7365b87298', - type: 'request', - sampled: true, - }, - }, - { - container: { - id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', - }, - parent: { - id: '49809ad3c26adf74', - }, - process: { - pid: 6, - title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java', - ppid: 1, - }, - agent: { - name: 'java', - ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb', - version: '1.14.1-SNAPSHOT', - }, - internal: { - sampler: { - value: 44, - }, - }, - destination: { - address: 'opbeans-go', - port: 3000, - }, - processor: { - name: 'transaction', - event: 'span', - }, - observer: { - hostname: 'f37f48d8b60b', - id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e', - type: 'apm-server', - ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9', - version: '8.0.0', - version_major: 8, - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T15:04:28.785Z', - ecs: { - version: '1.4.0', - }, - service: { - node: { - name: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', - }, - environment: 'production', - name: 'opbeans-java', - runtime: { - name: 'Java', - version: '10.0.2', - }, - language: { - name: 'Java', - version: '10.0.2', - }, - version: 'None', - }, - host: { - hostname: '4cf84d094553', - os: { - platform: 'Linux', - }, - ip: '172.19.0.9', - name: '4cf84d094553', - architecture: 'amd64', - }, - connection: { - hash: "{service.environment:'production'}/{service.name:'opbeans-java'}/{span.subtype:'http'}/{destination.address:'opbeans-go'}/{span.type:'external'}", - }, - transaction: { - id: '49809ad3c26adf74', - }, - timestamp: { - us: 1584975868785273, - }, - span: { - duration: { - us: 17530, - }, - subtype: 'http', - name: 'GET opbeans-go', - destination: { - service: { - resource: 'opbeans-go:3000', - name: 'http://opbeans-go:3000', - type: 'external', - }, - }, - http: { - response: { - status_code: 200, - }, - url: { - original: 'http://opbeans-go:3000/api/orders', - }, - }, - id: 'fc107f7b556eb49b', - type: 'external', - }, - }, - { - parent: { - id: '975c8d5bfd1dd20b', - }, - agent: { - name: 'go', - version: '1.7.2', - }, - processor: { - name: 'transaction', - event: 'span', - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T15:04:28.787Z', - service: { - node: { - name: 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29', - }, - environment: 'production', - name: 'opbeans-go', - runtime: { - name: 'gc', - version: 'go1.14.1', - }, - language: { - name: 'go', - version: 'go1.14.1', - }, - version: 'None', - }, - transaction: { - id: '975c8d5bfd1dd20b', - }, - timestamp: { - us: 1584975868787174, - }, - span: { - duration: { - us: 16250, - }, - subtype: 'http', - destination: { - service: { - resource: 'opbeans-python:3000', - name: 'http://opbeans-python:3000', - type: 'external', - }, - }, - name: 'GET opbeans-python:3000', - http: { - response: { - status_code: 200, - }, - url: { - original: 'http://opbeans-python:3000/api/orders', - }, - }, - id: 'daae24d83c269918', - type: 'external', - }, - }, - { - container: { - id: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', - }, - parent: { - id: '6fb0ff7365b87298', - }, - agent: { - name: 'python', - version: '5.5.2', - }, - processor: { - name: 'transaction', - event: 'span', - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T15:04:28.790Z', - service: { - node: { - name: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', - }, - environment: 'production', - framework: { - name: 'django', - version: '2.1.13', - }, - name: 'opbeans-python', - runtime: { - name: 'CPython', - version: '3.6.10', - }, - language: { - name: 'python', - version: '3.6.10', - }, - version: 'None', - }, - transaction: { - id: '6fb0ff7365b87298', - }, - timestamp: { - us: 1584975868790080, - }, - span: { - duration: { - us: 2519, - }, - subtype: 'postgresql', - name: 'SELECT FROM opbeans_order', - destination: { - service: { - resource: 'postgresql', - name: 'postgresql', - type: 'db', - }, - }, - action: 'query', - id: 'c9407abb4d08ead1', - type: 'db', - sync: true, - db: { - statement: - 'SELECT "opbeans_order"."id", "opbeans_order"."customer_id", "opbeans_customer"."full_name", "opbeans_order"."created_at" FROM "opbeans_order" INNER JOIN "opbeans_customer" ON ("opbeans_order"."customer_id" = "opbeans_customer"."id") LIMIT 1000', - type: 'sql', - }, - }, - }, - ], - exceedsMax: false, - errorDocs: [ - { - parent: { - id: '975c8d5bfd1dd20b', - }, - agent: { - name: 'go', - version: '1.7.2', - }, - error: { - culprit: 'logrusMiddleware', - log: { - level: 'error', - message: 'GET //api/products (502)', - }, - id: '1f3cb98206b5c54225cb7c8908a658da', - grouping_key: '4dba2ff58fe6c036a5dee2ce411e512a', - }, - processor: { - name: 'error', - event: 'error', - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T16:04:28.787Z', - service: { - node: { - name: 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29', - }, - environment: 'production', - name: 'opbeans-go', - runtime: { - name: 'gc', - version: 'go1.14.1', - }, - language: { - name: 'go', - version: 'go1.14.1', - }, - version: 'None', - }, - transaction: { - id: '975c8d5bfd1dd20b', - sampled: false, - }, - timestamp: { - us: 1584975868787052, - }, - }, - { - parent: { - id: '6fb0ff7365b87298', - }, - agent: { - name: 'python', - version: '5.5.2', - }, - error: { - culprit: 'logrusMiddleware', - log: { - level: 'error', - message: 'GET //api/products (502)', - }, - id: '1f3cb98206b5c54225cb7c8908a658d2', - grouping_key: '4dba2ff58fe6c036a5dee2ce411e512a', - }, - processor: { - name: 'error', - event: 'error', - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T16:04:28.790Z', - service: { - node: { - name: 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29', - }, - environment: 'production', - name: 'opbeans-python', - runtime: { - name: 'gc', - version: 'go1.14.1', - }, - version: 'None', - }, - transaction: { - id: '6fb0ff7365b87298', - sampled: false, - }, - timestamp: { - us: 1584975868790000, - }, - }, - ], -} as unknown as TraceAPIResponse; - -export const traceChildStartBeforeParent = { - traceDocs: [ - { - container: { - id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', - }, - process: { - pid: 6, - title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java', - ppid: 1, - }, - agent: { - name: 'java', - ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb', - version: '1.14.1-SNAPSHOT', - }, - internal: { - sampler: { - value: 46, - }, - }, - source: { - ip: '172.19.0.13', - }, - processor: { - name: 'transaction', - event: 'transaction', - }, - url: { - path: '/api/orders', - scheme: 'http', - port: 3000, - domain: '172.19.0.9', - full: 'http://172.19.0.9:3000/api/orders', - }, - observer: { - hostname: 'f37f48d8b60b', - id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e', - ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9', - type: 'apm-server', - version: '8.0.0', - version_major: 8, - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T15:04:28.785Z', - ecs: { - version: '1.4.0', - }, - service: { - node: { - name: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', - }, - environment: 'production', - name: 'opbeans-java', - runtime: { - name: 'Java', - version: '10.0.2', - }, - language: { - name: 'Java', - version: '10.0.2', - }, - version: 'None', - }, - host: { - hostname: '4cf84d094553', - os: { - platform: 'Linux', - }, - ip: '172.19.0.9', - name: '4cf84d094553', - architecture: 'amd64', - }, - http: { - request: { - headers: { - Accept: ['*/*'], - 'User-Agent': ['Python/3.7 aiohttp/3.3.2'], - Host: ['172.19.0.9:3000'], - 'Accept-Encoding': ['gzip, deflate'], - }, - method: 'get', - socket: { - encrypted: false, - remote_address: '172.19.0.13', - }, - body: { - original: '[REDACTED]', - }, - }, - response: { - headers: { - 'Transfer-Encoding': ['chunked'], - Date: ['Mon, 23 Mar 2020 15:04:28 GMT'], - 'Content-Type': ['application/json;charset=ISO-8859-1'], - }, - status_code: 200, - finished: true, - headers_sent: true, - }, - version: '1.1', - }, - client: { - ip: '172.19.0.13', - }, - transaction: { - duration: { - us: 18842, - }, - result: 'HTTP 2xx', - name: 'DispatcherServlet#doGet', - id: '49809ad3c26adf74', - span_count: { - dropped: 0, - started: 1, - }, - type: 'request', - sampled: true, - }, - user_agent: { - original: 'Python/3.7 aiohttp/3.3.2', - name: 'Other', - device: { - name: 'Other', - }, - }, - timestamp: { - us: 1584975868785000, - }, - }, - { - parent: { - id: 'fc107f7b556eb49b', - }, - agent: { - name: 'go', - version: '1.7.2', - }, - processor: { - name: 'transaction', - event: 'transaction', - }, - url: { - path: '/api/orders', - scheme: 'http', - port: 3000, - domain: 'opbeans-go', - full: 'http://opbeans-go:3000/api/orders', - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T15:04:28.787Z', - service: { - node: { - name: 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29', - }, - environment: 'production', - framework: { - name: 'gin', - version: 'v1.4.0', - }, - name: 'opbeans-go', - runtime: { - name: 'gc', - version: 'go1.14.1', - }, - language: { - name: 'go', - version: 'go1.14.1', - }, - version: 'None', - }, - transaction: { - duration: { - us: 16597, - }, - result: 'HTTP 2xx', - name: 'GET /api/orders', - id: '975c8d5bfd1dd20b', - span_count: { - dropped: 0, - started: 1, - }, - type: 'request', - sampled: true, - }, - timestamp: { - us: 1584975868787052, - }, - }, - { - parent: { - id: 'daae24d83c269918', - }, - agent: { - name: 'python', - version: '5.5.2', - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - timestamp: { - us: 1584975868780000, - }, - processor: { - name: 'transaction', - event: 'transaction', - }, - url: { - path: '/api/orders', - scheme: 'http', - port: 3000, - domain: 'opbeans-go', - full: 'http://opbeans-go:3000/api/orders', - }, - '@timestamp': '2020-03-23T15:04:28.788Z', - service: { - node: { - name: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', - }, - environment: 'production', - framework: { - name: 'django', - version: '2.1.13', - }, - name: 'opbeans-python', - runtime: { - name: 'CPython', - version: '3.6.10', - }, - language: { - name: 'python', - version: '3.6.10', - }, - version: 'None', - }, - transaction: { - result: 'HTTP 2xx', - duration: { - us: 1464, - }, - name: 'I started before my parent 😰', - span_count: { - dropped: 0, - started: 1, - }, - id: '6fb0ff7365b87298', - type: 'request', - sampled: true, - }, - }, - { - container: { - id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', - }, - parent: { - id: '49809ad3c26adf74', - }, - process: { - pid: 6, - title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java', - ppid: 1, - }, - agent: { - name: 'java', - ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb', - version: '1.14.1-SNAPSHOT', - }, - internal: { - sampler: { - value: 44, - }, - }, - destination: { - address: 'opbeans-go', - port: 3000, - }, - processor: { - name: 'transaction', - event: 'span', - }, - observer: { - hostname: 'f37f48d8b60b', - id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e', - type: 'apm-server', - ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9', - version: '8.0.0', - version_major: 8, - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T15:04:28.785Z', - ecs: { - version: '1.4.0', - }, - service: { - node: { - name: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', - }, - environment: 'production', - name: 'opbeans-java', - runtime: { - name: 'Java', - version: '10.0.2', - }, - language: { - name: 'Java', - version: '10.0.2', - }, - version: 'None', - }, - host: { - hostname: '4cf84d094553', - os: { - platform: 'Linux', - }, - ip: '172.19.0.9', - name: '4cf84d094553', - architecture: 'amd64', - }, - connection: { - hash: "{service.environment:'production'}/{service.name:'opbeans-java'}/{span.subtype:'http'}/{destination.address:'opbeans-go'}/{span.type:'external'}", - }, - transaction: { - id: '49809ad3c26adf74', - }, - timestamp: { - us: 1584975868785273, - }, - span: { - duration: { - us: 17530, - }, - subtype: 'http', - name: 'GET opbeans-go', - destination: { - service: { - resource: 'opbeans-go:3000', - name: 'http://opbeans-go:3000', - type: 'external', - }, - }, - http: { - response: { - status_code: 200, - }, - url: { - original: 'http://opbeans-go:3000/api/orders', - }, - }, - id: 'fc107f7b556eb49b', - type: 'external', - }, - }, - { - parent: { - id: '975c8d5bfd1dd20b', - }, - agent: { - name: 'go', - version: '1.7.2', - }, - processor: { - name: 'transaction', - event: 'span', - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T15:04:28.787Z', - service: { - node: { - name: 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29', - }, - environment: 'production', - name: 'opbeans-go', - runtime: { - name: 'gc', - version: 'go1.14.1', - }, - language: { - name: 'go', - version: 'go1.14.1', - }, - version: 'None', - }, - transaction: { - id: '975c8d5bfd1dd20b', - }, - timestamp: { - us: 1584975868787174, - }, - span: { - duration: { - us: 16250, - }, - subtype: 'http', - destination: { - service: { - resource: 'opbeans-python:3000', - name: 'http://opbeans-python:3000', - type: 'external', - }, - }, - name: 'I am his 👇🏻 parent 😡', - http: { - response: { - status_code: 200, - }, - url: { - original: 'http://opbeans-python:3000/api/orders', - }, - }, - id: 'daae24d83c269918', - type: 'external', - }, - }, - { - container: { - id: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', - }, - parent: { - id: '6fb0ff7365b87298', - }, - agent: { - name: 'python', - version: '5.5.2', - }, - processor: { - name: 'transaction', - event: 'span', - }, - trace: { - id: '513d33fafe99bbe6134749310c9b5322', - }, - '@timestamp': '2020-03-23T15:04:28.790Z', - service: { - node: { - name: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', - }, - environment: 'production', - framework: { - name: 'django', - version: '2.1.13', - }, - name: 'opbeans-python', - runtime: { - name: 'CPython', - version: '3.6.10', - }, - language: { - name: 'python', - version: '3.6.10', - }, - version: 'None', - }, - transaction: { - id: '6fb0ff7365b87298', - }, - timestamp: { - us: 1584975868781000, - }, - span: { - duration: { - us: 2519, - }, - subtype: 'postgresql', - name: 'I am using my parents skew 😇', - destination: { - service: { - resource: 'postgresql', - name: 'postgresql', - type: 'db', - }, - }, - action: 'query', - id: 'c9407abb4d08ead1', - type: 'db', - sync: true, - db: { - statement: - 'SELECT "opbeans_order"."id", "opbeans_order"."customer_id", "opbeans_customer"."full_name", "opbeans_order"."created_at" FROM "opbeans_order" INNER JOIN "opbeans_customer" ON ("opbeans_order"."customer_id" = "opbeans_customer"."id") LIMIT 1000', - type: 'sql', - }, - }, - }, - ], - exceedsMax: false, - errorDocs: [], -} as TraceAPIResponse; - -export const inferredSpans = { - traceDocs: [ - { - container: { - id: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', - }, - agent: { - name: 'java', - ephemeral_id: '1cb5c830-c677-4b13-b340-ab1502f527c3', - version: '1.15.1-SNAPSHOT', - }, - process: { - pid: 6, - title: '/opt/java/openjdk/bin/java', - ppid: 1, - }, - source: { - ip: '172.18.0.8', - }, - processor: { - name: 'transaction', - event: 'transaction', - }, - url: { - path: '/api/products/2', - scheme: 'http', - port: 3000, - domain: '172.18.0.7', - full: 'http://172.18.0.7:3000/api/products/2', - }, - observer: { - hostname: '7189f754b5a3', - id: 'f32d8d9f-a9f9-4355-8370-548dfd8024dc', - ephemeral_id: 'bff20764-0195-4f78-aa84-d799fc47b954', - type: 'apm-server', - version: '8.0.0', - version_major: 8, - }, - trace: { - id: '3b0dc77f3754e5bcb9da0e4c15e0db97', - }, - '@timestamp': '2020-04-09T11:36:00.786Z', - ecs: { - version: '1.5.0', - }, - service: { - node: { - name: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', - }, - environment: 'production', - name: 'opbeans-java', - runtime: { - name: 'Java', - version: '11.0.6', - }, - language: { - name: 'Java', - version: '11.0.6', - }, - version: 'None', - }, - host: { - hostname: 'fc2ae281f56f', - os: { - platform: 'Linux', - }, - ip: '172.18.0.7', - name: 'fc2ae281f56f', - architecture: 'amd64', - }, - client: { - ip: '172.18.0.8', - }, - http: { - request: { - headers: { - Accept: ['*/*'], - 'User-Agent': ['Python/3.7 aiohttp/3.3.2'], - Host: ['172.18.0.7:3000'], - 'Accept-Encoding': ['gzip, deflate'], - }, - method: 'get', - socket: { - encrypted: false, - remote_address: '172.18.0.8', - }, - }, - response: { - headers: { - 'Transfer-Encoding': ['chunked'], - Date: ['Thu, 09 Apr 2020 11:36:01 GMT'], - 'Content-Type': ['application/json;charset=UTF-8'], - }, - status_code: 200, - finished: true, - headers_sent: true, - }, - version: '1.1', - }, - user_agent: { - original: 'Python/3.7 aiohttp/3.3.2', - name: 'Other', - device: { - name: 'Other', - }, - }, - transaction: { - duration: { - us: 237537, - }, - result: 'HTTP 2xx', - name: 'APIRestController#product', - span_count: { - dropped: 0, - started: 3, - }, - id: 'f2387d37260d00bd', - type: 'request', - sampled: true, - }, - timestamp: { - us: 1586432160786001, - }, - }, - { - container: { - id: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', - }, - parent: { - id: 'f2387d37260d00bd', - }, - agent: { - name: 'java', - ephemeral_id: '1cb5c830-c677-4b13-b340-ab1502f527c3', - version: '1.15.1-SNAPSHOT', - }, - process: { - pid: 6, - title: '/opt/java/openjdk/bin/java', - ppid: 1, - }, - processor: { - name: 'transaction', - event: 'span', - }, - observer: { - hostname: '7189f754b5a3', - id: 'f32d8d9f-a9f9-4355-8370-548dfd8024dc', - ephemeral_id: 'bff20764-0195-4f78-aa84-d799fc47b954', - type: 'apm-server', - version: '8.0.0', - version_major: 8, - }, - trace: { - id: '3b0dc77f3754e5bcb9da0e4c15e0db97', - }, - '@timestamp': '2020-04-09T11:36:00.810Z', - ecs: { - version: '1.5.0', - }, - service: { - node: { - name: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', - }, - environment: 'production', - name: 'opbeans-java', - runtime: { - name: 'Java', - version: '11.0.6', - }, - language: { - name: 'Java', - version: '11.0.6', - }, - version: 'None', - }, - host: { - hostname: 'fc2ae281f56f', - os: { - platform: 'Linux', - }, - ip: '172.18.0.7', - name: 'fc2ae281f56f', - architecture: 'amd64', - }, - transaction: { - id: 'f2387d37260d00bd', - }, - span: { - duration: { - us: 204574, - }, - subtype: 'inferred', - name: 'ServletInvocableHandlerMethod#invokeAndHandle', - id: 'a5df600bd7bd5e38', - type: 'app', - }, - timestamp: { - us: 1586432160810441, - }, - }, - { - container: { - id: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', - }, - parent: { - id: 'a5df600bd7bd5e38', - }, - agent: { - name: 'java', - ephemeral_id: '1cb5c830-c677-4b13-b340-ab1502f527c3', - version: '1.15.1-SNAPSHOT', - }, - process: { - pid: 6, - title: '/opt/java/openjdk/bin/java', - ppid: 1, - }, - processor: { - name: 'transaction', - event: 'span', - }, - observer: { - hostname: '7189f754b5a3', - id: 'f32d8d9f-a9f9-4355-8370-548dfd8024dc', - type: 'apm-server', - ephemeral_id: 'bff20764-0195-4f78-aa84-d799fc47b954', - version: '8.0.0', - version_major: 8, - }, - trace: { - id: '3b0dc77f3754e5bcb9da0e4c15e0db97', - }, - '@timestamp': '2020-04-09T11:36:00.810Z', - ecs: { - version: '1.5.0', - }, - service: { - node: { - name: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', - }, - environment: 'production', - name: 'opbeans-java', - runtime: { - name: 'Java', - version: '11.0.6', - }, - language: { - name: 'Java', - version: '11.0.6', - }, - version: 'None', - }, - host: { - hostname: 'fc2ae281f56f', - os: { - platform: 'Linux', - }, - ip: '172.18.0.7', - name: 'fc2ae281f56f', - architecture: 'amd64', - }, - transaction: { - id: 'f2387d37260d00bd', - }, - timestamp: { - us: 1586432160810441, - }, - span: { - duration: { - us: 102993, - }, - stacktrace: [ - { - library_frame: true, - exclude_from_grouping: false, - filename: 'InvocableHandlerMethod.java', - line: { - number: -1, - }, - function: 'doInvoke', - }, - { - exclude_from_grouping: false, - library_frame: true, - filename: 'InvocableHandlerMethod.java', - line: { - number: -1, - }, - function: 'invokeForRequest', - }, - ], - subtype: 'inferred', - name: 'APIRestController#product', - id: '808dc34fc41ce522', - type: 'app', - }, - }, - { - container: { - id: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', - }, - parent: { - id: 'f2387d37260d00bd', - }, - agent: { - name: 'java', - ephemeral_id: '1cb5c830-c677-4b13-b340-ab1502f527c3', - version: '1.15.1-SNAPSHOT', - }, - process: { - pid: 6, - title: '/opt/java/openjdk/bin/java', - ppid: 1, - }, - processor: { - name: 'transaction', - event: 'span', - }, - labels: { - productId: '2', - }, - observer: { - hostname: '7189f754b5a3', - id: 'f32d8d9f-a9f9-4355-8370-548dfd8024dc', - ephemeral_id: 'bff20764-0195-4f78-aa84-d799fc47b954', - type: 'apm-server', - version: '8.0.0', - version_major: 8, - }, - trace: { - id: '3b0dc77f3754e5bcb9da0e4c15e0db97', - }, - '@timestamp': '2020-04-09T11:36:00.832Z', - ecs: { - version: '1.5.0', - }, - service: { - node: { - name: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', - }, - environment: 'production', - name: 'opbeans-java', - runtime: { - name: 'Java', - version: '11.0.6', - }, - language: { - name: 'Java', - version: '11.0.6', - }, - version: 'None', - }, - host: { - hostname: 'fc2ae281f56f', - os: { - platform: 'Linux', - }, - ip: '172.18.0.7', - name: 'fc2ae281f56f', - architecture: 'amd64', - }, - transaction: { - id: 'f2387d37260d00bd', - }, - timestamp: { - us: 1586432160832300, - }, - span: { - duration: { - us: 99295, - }, - name: 'OpenTracing product span', - id: '41226ae63af4f235', - type: 'unknown', - }, - child: { id: ['8d80de06aa11a6fc'] }, - }, - { - container: { - id: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', - }, - parent: { - id: '808dc34fc41ce522', - }, - process: { - pid: 6, - title: '/opt/java/openjdk/bin/java', - ppid: 1, - }, - agent: { - name: 'java', - ephemeral_id: '1cb5c830-c677-4b13-b340-ab1502f527c3', - version: '1.15.1-SNAPSHOT', - }, - processor: { - name: 'transaction', - event: 'span', - }, - observer: { - hostname: '7189f754b5a3', - id: 'f32d8d9f-a9f9-4355-8370-548dfd8024dc', - ephemeral_id: 'bff20764-0195-4f78-aa84-d799fc47b954', - type: 'apm-server', - version: '8.0.0', - version_major: 8, - }, - trace: { - id: '3b0dc77f3754e5bcb9da0e4c15e0db97', - }, - '@timestamp': '2020-04-09T11:36:00.859Z', - ecs: { - version: '1.5.0', - }, - service: { - node: { - name: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', - }, - environment: 'production', - name: 'opbeans-java', - runtime: { - name: 'Java', - version: '11.0.6', - }, - language: { - name: 'Java', - version: '11.0.6', - }, - version: 'None', - }, - host: { - hostname: 'fc2ae281f56f', - os: { - platform: 'Linux', - }, - ip: '172.18.0.7', - name: 'fc2ae281f56f', - architecture: 'amd64', - }, - transaction: { - id: 'f2387d37260d00bd', - }, - timestamp: { - us: 1586432160859600, - }, - span: { - duration: { - us: 53835, - }, - subtype: 'inferred', - name: 'Loader#executeQueryStatement', - id: '8d80de06aa11a6fc', - type: 'app', - }, - }, - { - container: { - id: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', - }, - parent: { - id: '41226ae63af4f235', - }, - agent: { - name: 'java', - ephemeral_id: '1cb5c830-c677-4b13-b340-ab1502f527c3', - version: '1.15.1-SNAPSHOT', - }, - process: { - pid: 6, - title: '/opt/java/openjdk/bin/java', - ppid: 1, - }, - destination: { - address: 'postgres', - port: 5432, - }, - processor: { - name: 'transaction', - event: 'span', - }, - observer: { - hostname: '7189f754b5a3', - id: 'f32d8d9f-a9f9-4355-8370-548dfd8024dc', - ephemeral_id: 'bff20764-0195-4f78-aa84-d799fc47b954', - type: 'apm-server', - version: '8.0.0', - version_major: 8, - }, - trace: { - id: '3b0dc77f3754e5bcb9da0e4c15e0db97', - }, - '@timestamp': '2020-04-09T11:36:00.903Z', - ecs: { - version: '1.5.0', - }, - service: { - node: { - name: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', - }, - environment: 'production', - name: 'opbeans-java', - runtime: { - name: 'Java', - version: '11.0.6', - }, - language: { - name: 'Java', - version: '11.0.6', - }, - version: 'None', - }, - host: { - hostname: 'fc2ae281f56f', - os: { - platform: 'Linux', - }, - ip: '172.18.0.7', - name: 'fc2ae281f56f', - architecture: 'amd64', - }, - transaction: { - id: 'f2387d37260d00bd', - }, - timestamp: { - us: 1586432160903236, - }, - span: { - duration: { - us: 10211, - }, - subtype: 'postgresql', - destination: { - service: { - resource: 'postgresql', - name: 'postgresql', - type: 'db', - }, - }, - name: 'SELECT FROM products', - action: 'query', - id: '3708d5623658182f', - type: 'db', - db: { - statement: - 'select product0_.id as col_0_0_, product0_.sku as col_1_0_, product0_.name as col_2_0_, product0_.description as col_3_0_, product0_.cost as col_4_0_, product0_.selling_price as col_5_0_, product0_.stock as col_6_0_, producttyp1_.id as col_7_0_, producttyp1_.name as col_8_0_, (select sum(orderline2_.amount) from order_lines orderline2_ where orderline2_.product_id=product0_.id) as col_9_0_ from products product0_ left outer join product_types producttyp1_ on product0_.type_id=producttyp1_.id where product0_.id=?', - type: 'sql', - user: { - name: 'postgres', - }, - }, - }, - }, - { - container: { - id: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', - }, - parent: { - id: '41226ae63af4f235', - }, - process: { - pid: 6, - title: '/opt/java/openjdk/bin/java', - ppid: 1, - }, - agent: { - name: 'java', - ephemeral_id: '1cb5c830-c677-4b13-b340-ab1502f527c3', - version: '1.15.1-SNAPSHOT', - }, - destination: { - address: 'postgres', - port: 5432, - }, - processor: { - name: 'transaction', - event: 'span', - }, - observer: { - hostname: '7189f754b5a3', - id: 'f32d8d9f-a9f9-4355-8370-548dfd8024dc', - ephemeral_id: 'bff20764-0195-4f78-aa84-d799fc47b954', - type: 'apm-server', - version: '8.0.0', - version_major: 8, - }, - trace: { - id: '3b0dc77f3754e5bcb9da0e4c15e0db97', - }, - '@timestamp': '2020-04-09T11:36:00.859Z', - ecs: { - version: '1.5.0', - }, - service: { - node: { - name: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', - }, - environment: 'production', - name: 'opbeans-java', - runtime: { - name: 'Java', - version: '11.0.6', - }, - language: { - name: 'Java', - version: '11.0.6', - }, - version: 'None', - }, - host: { - hostname: 'fc2ae281f56f', - os: { - platform: 'Linux', - }, - ip: '172.18.0.7', - name: 'fc2ae281f56f', - architecture: 'amd64', - }, - transaction: { - id: 'f2387d37260d00bd', - }, - timestamp: { - us: 1586432160859508, - }, - span: { - duration: { - us: 4503, - }, - subtype: 'postgresql', - destination: { - service: { - resource: 'postgresql', - name: 'postgresql', - type: 'db', - }, - }, - name: 'empty query', - action: 'query', - id: '9871cfd612368932', - type: 'db', - db: { - rows_affected: 0, - statement: '(empty query)', - type: 'sql', - user: { - name: 'postgres', - }, - }, - }, - }, - ], - exceedsMax: false, - errorDocs: [], -} as TraceAPIResponse; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.stories.data.ts b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.stories.data.ts new file mode 100644 index 0000000000000..6cca3726a7d00 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.stories.data.ts @@ -0,0 +1,5865 @@ +/* + * Copyright 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 { Location } from 'history'; +import type { ApmUrlParams } from '../../../../../context/url_params_context/types'; +import { APIReturnType } from '../../../../../services/rest/createCallApmApi'; + +export const location = { + pathname: '/services/opbeans-go/transactions/view', + search: + '?rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0&kuery=service.name%253A%2520%2522opbeans-java%2522%2520or%2520service.name%2520%253A%2520%2522opbeans-go%2522&traceId=513d33fafe99bbe6134749310c9b5322&transactionId=975c8d5bfd1dd20b&transactionName=GET%20%2Fapi%2Forders&transactionType=request', + hash: '', +} as Location; + +type TraceAPIResponse = APIReturnType<'GET /internal/apm/traces/{traceId}'>; + +export const urlParams = { + start: '2020-03-22T15:16:38.742Z', + end: '2020-03-23T15:16:38.742Z', + rangeFrom: 'now-24h', + rangeTo: 'now', + refreshPaused: true, + refreshInterval: 0, + page: 0, + transactionId: '975c8d5bfd1dd20b', + traceId: '513d33fafe99bbe6134749310c9b5322', + transactionName: 'GET /api/orders', + transactionType: 'request', + processorEvent: 'transaction', + serviceName: 'opbeans-go', +} as ApmUrlParams; + +export const simpleTrace = { + traceDocs: [ + { + container: { + id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', + }, + process: { + pid: 6, + title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java', + ppid: 1, + }, + agent: { + name: 'java', + ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb', + version: '1.14.1-SNAPSHOT', + }, + internal: { + sampler: { + value: 46, + }, + }, + source: { + ip: '172.19.0.13', + }, + processor: { + name: 'transaction', + event: 'transaction', + }, + url: { + path: '/api/orders', + scheme: 'http', + port: 3000, + domain: '172.19.0.9', + full: 'http://172.19.0.9:3000/api/orders', + }, + observer: { + hostname: 'f37f48d8b60b', + id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e', + ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9', + type: 'apm-server', + version: '8.0.0', + version_major: 8, + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T15:04:28.785Z', + ecs: { + version: '1.4.0', + }, + service: { + node: { + name: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', + }, + environment: 'production', + name: 'opbeans-java', + runtime: { + name: 'Java', + version: '10.0.2', + }, + language: { + name: 'Java', + version: '10.0.2', + }, + version: 'None', + }, + host: { + hostname: '4cf84d094553', + os: { + platform: 'Linux', + }, + ip: '172.19.0.9', + name: '4cf84d094553', + architecture: 'amd64', + }, + http: { + request: { + headers: { + Accept: ['*/*'], + 'User-Agent': ['Python/3.7 aiohttp/3.3.2'], + Host: ['172.19.0.9:3000'], + 'Accept-Encoding': ['gzip, deflate'], + }, + method: 'get', + socket: { + encrypted: false, + remote_address: '172.19.0.13', + }, + body: { + original: '[REDACTED]', + }, + }, + response: { + headers: { + 'Transfer-Encoding': ['chunked'], + Date: ['Mon, 23 Mar 2020 15:04:28 GMT'], + 'Content-Type': ['application/json;charset=ISO-8859-1'], + }, + status_code: 200, + finished: true, + headers_sent: true, + }, + version: '1.1', + }, + client: { + ip: '172.19.0.13', + }, + transaction: { + duration: { + us: 18842, + }, + result: 'HTTP 2xx', + name: 'DispatcherServlet#doGet', + id: '49809ad3c26adf74', + span_count: { + dropped: 0, + started: 1, + }, + type: 'request', + sampled: true, + }, + user_agent: { + original: 'Python/3.7 aiohttp/3.3.2', + name: 'Other', + device: { + name: 'Other', + }, + }, + timestamp: { + us: 1584975868785000, + }, + }, + { + parent: { + id: 'fc107f7b556eb49b', + }, + agent: { + name: 'go', + version: '1.7.2', + }, + processor: { + name: 'transaction', + event: 'transaction', + }, + url: { + path: '/api/orders', + scheme: 'http', + port: 3000, + domain: 'opbeans-go', + full: 'http://opbeans-go:3000/api/orders', + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T15:04:28.787Z', + service: { + node: { + name: 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29', + }, + environment: 'production', + framework: { + name: 'gin', + version: 'v1.4.0', + }, + name: 'opbeans-go', + runtime: { + name: 'gc', + version: 'go1.14.1', + }, + language: { + name: 'go', + version: 'go1.14.1', + }, + version: 'None', + }, + transaction: { + duration: { + us: 16597, + }, + result: 'HTTP 2xx', + name: 'GET /api/orders', + id: '975c8d5bfd1dd20b', + span_count: { + dropped: 0, + started: 1, + }, + type: 'request', + sampled: true, + }, + timestamp: { + us: 1584975868787052, + }, + }, + { + parent: { + id: 'daae24d83c269918', + }, + agent: { + name: 'python', + version: '5.5.2', + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + timestamp: { + us: 1584975868788603, + }, + processor: { + name: 'transaction', + event: 'transaction', + }, + url: { + path: '/api/orders', + scheme: 'http', + port: 3000, + domain: 'opbeans-go', + full: 'http://opbeans-go:3000/api/orders', + }, + '@timestamp': '2020-03-23T15:04:28.788Z', + service: { + node: { + name: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', + }, + environment: 'production', + framework: { + name: 'django', + version: '2.1.13', + }, + name: 'opbeans-python', + runtime: { + name: 'CPython', + version: '3.6.10', + }, + language: { + name: 'python', + version: '3.6.10', + }, + version: 'None', + }, + transaction: { + result: 'HTTP 2xx', + duration: { + us: 14648, + }, + name: 'GET opbeans.views.orders', + span_count: { + dropped: 0, + started: 1, + }, + id: '6fb0ff7365b87298', + type: 'request', + sampled: true, + }, + }, + { + container: { + id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', + }, + parent: { + id: '49809ad3c26adf74', + }, + process: { + pid: 6, + title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java', + ppid: 1, + }, + agent: { + name: 'java', + ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb', + version: '1.14.1-SNAPSHOT', + }, + internal: { + sampler: { + value: 44, + }, + }, + destination: { + address: 'opbeans-go', + port: 3000, + }, + processor: { + name: 'transaction', + event: 'span', + }, + observer: { + hostname: 'f37f48d8b60b', + id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e', + type: 'apm-server', + ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9', + version: '8.0.0', + version_major: 8, + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T15:04:28.785Z', + ecs: { + version: '1.4.0', + }, + service: { + node: { + name: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', + }, + environment: 'production', + name: 'opbeans-java', + runtime: { + name: 'Java', + version: '10.0.2', + }, + language: { + name: 'Java', + version: '10.0.2', + }, + version: 'None', + }, + host: { + hostname: '4cf84d094553', + os: { + platform: 'Linux', + }, + ip: '172.19.0.9', + name: '4cf84d094553', + architecture: 'amd64', + }, + connection: { + hash: "{service.environment:'production'}/{service.name:'opbeans-java'}/{span.subtype:'http'}/{destination.address:'opbeans-go'}/{span.type:'external'}", + }, + transaction: { + id: '49809ad3c26adf74', + }, + timestamp: { + us: 1584975868785273, + }, + span: { + duration: { + us: 17530, + }, + subtype: 'http', + name: 'GET opbeans-go', + destination: { + service: { + resource: 'opbeans-go:3000', + name: 'http://opbeans-go:3000', + type: 'external', + }, + }, + http: { + response: { + status_code: 200, + }, + url: { + original: 'http://opbeans-go:3000/api/orders', + }, + }, + id: 'fc107f7b556eb49b', + type: 'external', + }, + }, + { + parent: { + id: '975c8d5bfd1dd20b', + }, + agent: { + name: 'go', + version: '1.7.2', + }, + processor: { + name: 'transaction', + event: 'span', + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T15:04:28.787Z', + service: { + node: { + name: 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29', + }, + environment: 'production', + name: 'opbeans-go', + runtime: { + name: 'gc', + version: 'go1.14.1', + }, + language: { + name: 'go', + version: 'go1.14.1', + }, + version: 'None', + }, + transaction: { + id: '975c8d5bfd1dd20b', + }, + timestamp: { + us: 1584975868787174, + }, + span: { + duration: { + us: 16250, + }, + subtype: 'http', + destination: { + service: { + resource: 'opbeans-python:3000', + name: 'http://opbeans-python:3000', + type: 'external', + }, + }, + name: 'GET opbeans-python:3000', + http: { + response: { + status_code: 200, + }, + url: { + original: 'http://opbeans-python:3000/api/orders', + }, + }, + id: 'daae24d83c269918', + type: 'external', + }, + }, + { + container: { + id: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', + }, + parent: { + id: '6fb0ff7365b87298', + }, + agent: { + name: 'python', + version: '5.5.2', + }, + processor: { + name: 'transaction', + event: 'span', + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T15:04:28.790Z', + service: { + node: { + name: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', + }, + environment: 'production', + framework: { + name: 'django', + version: '2.1.13', + }, + name: 'opbeans-python', + runtime: { + name: 'CPython', + version: '3.6.10', + }, + language: { + name: 'python', + version: '3.6.10', + }, + version: 'None', + }, + transaction: { + id: '6fb0ff7365b87298', + }, + timestamp: { + us: 1584975868790080, + }, + span: { + duration: { + us: 2519, + }, + subtype: 'postgresql', + name: 'SELECT FROM opbeans_order', + destination: { + service: { + resource: 'postgresql', + name: 'postgresql', + type: 'db', + }, + }, + action: 'query', + id: 'c9407abb4d08ead1', + type: 'db', + sync: true, + db: { + statement: + 'SELECT "opbeans_order"."id", "opbeans_order"."customer_id", "opbeans_customer"."full_name", "opbeans_order"."created_at" FROM "opbeans_order" INNER JOIN "opbeans_customer" ON ("opbeans_order"."customer_id" = "opbeans_customer"."id") LIMIT 1000', + type: 'sql', + }, + }, + }, + ], + exceedsMax: false, + errorDocs: [], +} as TraceAPIResponse; + +export const manyChildrenWithSameLength = { + exceedsMax: false, + traceDocs: [ + { + container: { + id: '46721e28e45ec1926798491069d8585865b031b4eaa9800e35d06fef6be5e170', + }, + kubernetes: { + pod: { + uid: '900f3cac-eb7c-4308-9376-f644f173c3ee', + }, + }, + process: { + args: ['-C', 'config/puma.rb'], + pid: 38, + title: '/usr/local/bundle/bin/puma', + }, + agent: { + name: 'ruby', + version: '4.3.0', + }, + processor: { + name: 'transaction', + event: 'transaction', + }, + url: { + path: '/api/products/3', + scheme: 'http', + port: 3000, + domain: '10.15.245.224', + full: 'http://10.15.245.224:3000/api/products/3', + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'projects/8560181848/machineTypes/n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-zvs6p', + id: '69a7066f-46d2-42c4-a4cc-8400f60bf2b5', + ephemeral_id: '0ab88569-c301-40e9-8e78-cac7c1dac2bc', + type: 'apm-server', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + '@timestamp': '2021-10-19T13:57:02.536Z', + ecs: { + version: '1.11.0', + }, + service: { + node: { + name: '46721e28e45ec1926798491069d8585865b031b4eaa9800e35d06fef6be5e170', + }, + environment: 'production', + framework: { + name: 'Ruby on Rails', + version: '6.1.4.1', + }, + name: 'opbeans-ruby', + runtime: { + name: 'ruby', + version: '2.7.3', + }, + language: { + name: 'ruby', + version: '2.7.3', + }, + version: '2021-10-14 17:49:53', + }, + host: { + os: { + platform: 'linux', + }, + ip: '10.12.0.22', + architecture: 'x86_64', + }, + http: { + request: { + headers: { + Accept: ['*/*'], + Version: ['HTTP/1.1'], + 'User-Agent': ['Python/3.7 aiohttp/3.3.2'], + Host: ['10.15.245.224:3000'], + 'Accept-Encoding': ['gzip, deflate'], + }, + method: 'GET', + env: { + GATEWAY_INTERFACE: 'CGI/1.2', + ORIGINAL_FULLPATH: '/api/products/3', + SERVER_PORT: '3000', + SERVER_PROTOCOL: 'HTTP/1.1', + REMOTE_ADDR: '10.12.6.45', + REQUEST_URI: '/api/products/3', + ORIGINAL_SCRIPT_NAME: '', + SERVER_SOFTWARE: 'puma 5.5.0 Zawgyi', + QUERY_STRING: '', + SCRIPT_NAME: '', + REQUEST_METHOD: 'GET', + SERVER_NAME: '10.15.245.224', + REQUEST_PATH: '/api/products/3', + PATH_INFO: '/api/products/3', + ROUTES_9240_SCRIPT_NAME: '', + }, + body: { + original: '[SKIPPED]', + }, + }, + response: { + headers: { + 'Content-Type': ['application/json;charset=UTF-8'], + }, + status_code: 500, + finished: true, + headers_sent: true, + }, + version: '1.1', + }, + event: { + ingested: '2021-10-19T13:57:12.417144879Z', + outcome: 'failure', + }, + transaction: { + duration: { + us: 13359, + }, + result: 'HTTP 5xx', + name: 'Rack', + span_count: { + dropped: 0, + started: 1, + }, + id: '9a7f717439921d39', + type: 'request', + sampled: true, + }, + user_agent: { + original: 'Python/3.7 aiohttp/3.3.2', + name: 'Python aiohttp', + device: { + name: 'Other', + type: 'Other', + }, + version: '3.3.2', + }, + timestamp: { + us: 1634651822536408, + }, + }, + { + container: { + id: 'e7b69f99cb7523bedea6d7c97b684cf4b7ff458d0cba1efb1ac843300b3bf3c7', + }, + kubernetes: { + pod: { + uid: 'c5169b50-f3b3-4693-8e4b-150fca17c333', + name: 'opbeans-go-5d795ddf6b-rhlvf', + }, + }, + parent: { + id: '4eeaa6dfbfd047cd', + }, + agent: { + name: 'go', + version: '1.14.0', + }, + source: { + ip: '10.12.0.22', + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-lmf6c', + id: '7eedab18-1171-4a1b-a590-975e13fd103a', + type: 'apm-server', + ephemeral_id: '90034868-48e6-418c-8ab4-6616b403bca7', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + ecs: { + version: '1.11.0', + }, + host: { + os: { + platform: 'linux', + }, + ip: '10.12.0.14', + architecture: 'amd64', + }, + client: { + ip: '10.12.0.22', + }, + event: { + ingested: '2021-10-19T13:57:05.413190788Z', + outcome: 'failure', + }, + user_agent: { + original: 'http.rb/5.0.2', + name: 'Other', + device: { + name: 'Generic Feature Phone', + type: 'Other', + }, + }, + timestamp: { + us: 1634651822536408, + }, + process: { + args: [ + '/opbeans-go', + '-log-level=debug', + '-log-json', + '-listen=:3000', + '-frontend=/opbeans-frontend', + '-db=postgres:', + '-cache=redis://redis-master:6379', + ], + pid: 1, + title: 'opbeans-go', + ppid: 0, + }, + processor: { + name: 'transaction', + event: 'transaction', + }, + url: { + path: '/api/products/3', + scheme: 'http', + port: 3000, + domain: 'opbeans', + full: 'http://opbeans:3000/api/products/3', + }, + '@timestamp': '2021-10-19T13:57:02.536Z', + service: { + node: { + name: 'e7b69f99cb7523bedea6d7c97b684cf4b7ff458d0cba1efb1ac843300b3bf3c7', + }, + environment: 'testing', + framework: { + name: 'gin', + version: 'v1.7.3', + }, + name: 'opbeans-go', + runtime: { + name: 'gc', + version: 'go1.17.2', + }, + language: { + name: 'go', + version: 'go1.17.2', + }, + version: '2021-10-14 17:49:50', + }, + http: { + request: { + headers: { + Connection: ['close'], + 'User-Agent': ['http.rb/5.0.2'], + 'Elastic-Apm-Traceparent': [ + '00-d5e80ae688f1fef91533f02dd2bdc769-4eeaa6dfbfd047cd-01', + ], + Tracestate: ['es=s:1.0'], + Traceparent: [ + '00-d5e80ae688f1fef91533f02dd2bdc769-4eeaa6dfbfd047cd-01', + ], + }, + method: 'GET', + }, + response: { + headers: { + Date: ['Tue, 19 Oct 2021 13:57:02 GMT'], + 'Content-Type': ['application/json;charset=UTF-8'], + }, + status_code: 500, + }, + version: '1.1', + }, + transaction: { + result: 'HTTP 5xx', + duration: { + us: 13359, + }, + name: 'GET /api/products/:id', + id: '9f50f43e924d0b46', + span_count: { + dropped: 0, + started: 3, + }, + type: 'request', + sampled: true, + }, + }, + { + container: { + id: '015d1127421e2c3d42a0fb031fc75e989813f58973143b6c7e33dca6ccc6f31b', + }, + parent: { + id: '8d099ab4fcec4ab9', + }, + kubernetes: { + pod: { + uid: '459a6abf-198e-4107-b4dd-b0ae826755ab', + name: 'opbeans-go-nsn-69b89c4598-xsvgh', + }, + }, + agent: { + name: 'go', + version: '1.14.0', + }, + source: { + ip: '10.12.0.14', + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-zvs6p', + id: '69a7066f-46d2-42c4-a4cc-8400f60bf2b5', + ephemeral_id: '0ab88569-c301-40e9-8e78-cac7c1dac2bc', + type: 'apm-server', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + ecs: { + version: '1.11.0', + }, + host: { + os: { + platform: 'linux', + }, + ip: '10.12.0.13', + architecture: 'amd64', + }, + client: { + ip: '10.12.0.22', + }, + event: { + ingested: '2021-10-19T13:57:08.267103644Z', + outcome: 'failure', + }, + user_agent: { + original: 'http.rb/5.0.2', + name: 'Other', + device: { + name: 'Generic Feature Phone', + type: 'Other', + }, + }, + timestamp: { + us: 1634651822536408, + }, + process: { + args: [ + '/opbeans-go', + '-log-level=debug', + '-log-json', + '-listen=:3000', + '-frontend=/opbeans-frontend', + '-db=postgres:', + '-cache=redis://redis-master:6379', + ], + pid: 1, + title: 'opbeans-go', + ppid: 0, + }, + processor: { + name: 'transaction', + event: 'transaction', + }, + url: { + path: '/api/products/3', + scheme: 'http', + port: 3000, + domain: 'opbeans', + full: 'http://opbeans:3000/api/products/3', + }, + '@timestamp': '2021-10-19T13:57:02.536Z', + service: { + node: { + name: '015d1127421e2c3d42a0fb031fc75e989813f58973143b6c7e33dca6ccc6f31b', + }, + environment: 'testing', + framework: { + name: 'gin', + version: 'v1.7.3', + }, + name: 'opbeans-go', + runtime: { + name: 'gc', + version: 'go1.17.2', + }, + language: { + name: 'go', + version: 'go1.17.2', + }, + version: '2021-10-14 17:49:50', + }, + http: { + request: { + headers: { + 'User-Agent': ['http.rb/5.0.2'], + 'X-Forwarded-For': ['10.12.0.22'], + 'Accept-Encoding': ['gzip'], + 'Elastic-Apm-Traceparent': [ + '00-d5e80ae688f1fef91533f02dd2bdc769-8d099ab4fcec4ab9-01', + ], + Tracestate: ['es=s:1.0'], + Traceparent: [ + '00-d5e80ae688f1fef91533f02dd2bdc769-8d099ab4fcec4ab9-01', + ], + }, + method: 'GET', + }, + response: { + headers: { + Date: ['Tue, 19 Oct 2021 13:57:02 GMT'], + 'Content-Type': ['application/json;charset=UTF-8'], + }, + status_code: 500, + }, + version: '1.1', + }, + transaction: { + result: 'HTTP 5xx', + duration: { + us: 13359, + }, + name: 'GET /api/products/:id', + span_count: { + dropped: 0, + started: 3, + }, + id: 'b7801be83bcdc972', + type: 'request', + sampled: true, + }, + }, + { + container: { + id: '59036ecb70908dfec4e03edc477f6875d08677871b4af0db3144373802d00cb1', + }, + kubernetes: { + pod: { + uid: '878bab2a-1309-44ae-a0e2-c98a0b187da1', + name: 'opbeans-java-5f45d77dd8-h8bnb', + }, + }, + parent: { + id: '35e3637e26919055', + }, + agent: { + name: 'java', + ephemeral_id: '75e36588-9adb-4bb0-bfee-a333b1c57e67', + version: 'unknown', + }, + source: { + ip: '10.12.0.13', + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-lmf6c', + id: '7eedab18-1171-4a1b-a590-975e13fd103a', + ephemeral_id: '90034868-48e6-418c-8ab4-6616b403bca7', + type: 'apm-server', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + ecs: { + version: '1.11.0', + }, + host: { + os: { + platform: 'Linux', + }, + ip: '10.12.0.15', + architecture: 'amd64', + }, + client: { + ip: '10.12.0.22', + }, + event: { + ingested: '2021-10-19T13:57:10.382829210Z', + outcome: 'failure', + }, + user_agent: { + original: 'http.rb/5.0.2', + name: 'Other', + device: { + name: 'Generic Feature Phone', + type: 'Other', + }, + }, + timestamp: { + us: 1634651822536408, + }, + process: { + pid: 7, + title: '/opt/java/openjdk/bin/java', + ppid: 1, + }, + processor: { + name: 'transaction', + event: 'transaction', + }, + url: { + path: '/api/products/3', + scheme: 'http', + port: 3000, + domain: 'opbeans', + full: 'http://opbeans:3000/api/products/3', + }, + '@timestamp': '2021-10-19T13:57:02.536Z', + service: { + node: { + name: '59036ecb70908dfec4e03edc477f6875d08677871b4af0db3144373802d00cb1', + }, + environment: 'production', + framework: { + name: 'Spring Web MVC', + version: '5.0.6.RELEASE', + }, + name: 'opbeans-java', + runtime: { + name: 'Java', + version: '11.0.11', + }, + language: { + name: 'Java', + version: '11.0.11', + }, + version: '2021-10-14 17:49:52', + }, + http: { + request: { + headers: { + 'User-Agent': ['http.rb/5.0.2'], + 'X-Forwarded-For': ['10.12.0.22, 10.12.0.14'], + Host: ['opbeans:3000'], + 'Accept-Encoding': ['gzip'], + 'Elastic-Apm-Traceparent': [ + '00-d5e80ae688f1fef91533f02dd2bdc769-35e3637e26919055-01', + ], + Tracestate: ['es=s:1.0'], + Traceparent: [ + '00-d5e80ae688f1fef91533f02dd2bdc769-35e3637e26919055-01', + ], + }, + method: 'GET', + }, + response: { + status_code: 500, + finished: true, + headers_sent: true, + }, + version: '1.1', + }, + transaction: { + duration: { + us: 13359, + }, + result: 'HTTP 5xx', + name: 'APIRestController#product', + id: '2c30263c4ad8fe8b', + span_count: { + dropped: 0, + started: 3, + }, + type: 'request', + sampled: true, + }, + }, + { + parent: { + id: '9a7f717439921d39', + }, + agent: { + name: 'ruby', + version: '4.3.0', + }, + destination: { + address: 'opbeans', + port: 3000, + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'projects/8560181848/machineTypes/n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-lmf6c', + id: '7eedab18-1171-4a1b-a590-975e13fd103a', + ephemeral_id: '90034868-48e6-418c-8ab4-6616b403bca7', + type: 'apm-server', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + ecs: { + version: '1.11.0', + }, + event: { + outcome: 'failure', + }, + timestamp: { + us: 1634651822536408, + }, + processor: { + name: 'transaction', + event: 'span', + }, + url: { + original: 'http://opbeans:3000/api/products/3', + }, + '@timestamp': '2021-10-19T13:57:02.536Z', + service: { + environment: 'production', + name: 'opbeans-ruby', + }, + http: { + request: { + method: 'GET', + }, + response: { + status_code: 500, + }, + }, + transaction: { + id: '9a7f717439921d39', + }, + span: { + duration: { + us: 13359, + }, + stacktrace: [ + { + exclude_from_grouping: false, + library_frame: true, + filename: 'elastic_apm.rb', + abs_path: + '/usr/local/bundle/gems/elastic-apm-4.3.0/lib/elastic_apm.rb', + line: { + number: 235, + }, + function: 'tap', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'elastic_apm.rb', + abs_path: + '/usr/local/bundle/gems/elastic-apm-4.3.0/lib/elastic_apm.rb', + line: { + number: 235, + }, + function: 'start_span', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'elastic_apm.rb', + abs_path: + '/usr/local/bundle/gems/elastic-apm-4.3.0/lib/elastic_apm.rb', + line: { + number: 287, + }, + function: 'with_span', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'elastic_apm/spies/http.rb', + abs_path: + '/usr/local/bundle/gems/elastic-apm-4.3.0/lib/elastic_apm/spies/http.rb', + line: { + number: 45, + }, + function: 'perform', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: '/usr/local/bundle/gems/http-5.0.2/lib/http/client.rb', + filename: 'http/client.rb', + line: { + number: 31, + }, + function: 'request', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'http/chainable.rb', + abs_path: '/usr/local/bundle/gems/http-5.0.2/lib/http/chainable.rb', + line: { + number: 75, + }, + function: 'request', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'http/chainable.rb', + abs_path: '/usr/local/bundle/gems/http-5.0.2/lib/http/chainable.rb', + line: { + number: 20, + }, + function: 'get', + }, + { + exclude_from_grouping: false, + filename: 'opbeans_shuffle.rb', + abs_path: '/app/lib/opbeans_shuffle.rb', + line: { + number: 23, + context: ' resp = HTTP.get("#{lucky_winner}#{path}")\n', + }, + function: 'block in call', + context: { + pre: ['\n', ' Timeout.timeout(15) do\n'], + post: ['\n', ' [\n'], + }, + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'timeout.rb', + abs_path: '/usr/local/lib/ruby/2.7.0/timeout.rb', + line: { + number: 95, + }, + function: 'block in timeout', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'timeout.rb', + abs_path: '/usr/local/lib/ruby/2.7.0/timeout.rb', + line: { + number: 33, + }, + function: 'block in catch', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: '/usr/local/lib/ruby/2.7.0/timeout.rb', + filename: 'timeout.rb', + line: { + number: 33, + }, + function: 'catch', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'timeout.rb', + abs_path: '/usr/local/lib/ruby/2.7.0/timeout.rb', + line: { + number: 33, + }, + function: 'catch', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'timeout.rb', + abs_path: '/usr/local/lib/ruby/2.7.0/timeout.rb', + line: { + number: 110, + }, + function: 'timeout', + }, + { + exclude_from_grouping: false, + filename: 'opbeans_shuffle.rb', + abs_path: '/app/lib/opbeans_shuffle.rb', + line: { + number: 22, + context: ' Timeout.timeout(15) do\n', + }, + function: 'call', + context: { + pre: [' end\n', '\n'], + post: [ + ' resp = HTTP.get("#{lucky_winner}#{path}")\n', + '\n', + ], + }, + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'elastic_apm/middleware.rb', + abs_path: + '/usr/local/bundle/gems/elastic-apm-4.3.0/lib/elastic_apm/middleware.rb', + line: { + number: 36, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rails/engine.rb', + abs_path: + '/usr/local/bundle/gems/railties-6.1.4.1/lib/rails/engine.rb', + line: { + number: 539, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'puma/configuration.rb', + abs_path: + '/usr/local/bundle/gems/puma-5.5.0/lib/puma/configuration.rb', + line: { + number: 249, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: '/usr/local/bundle/gems/puma-5.5.0/lib/puma/request.rb', + filename: 'puma/request.rb', + line: { + number: 77, + }, + function: 'block in handle_request', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'puma/thread_pool.rb', + abs_path: + '/usr/local/bundle/gems/puma-5.5.0/lib/puma/thread_pool.rb', + line: { + number: 340, + }, + function: 'with_force_shutdown', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'puma/request.rb', + abs_path: '/usr/local/bundle/gems/puma-5.5.0/lib/puma/request.rb', + line: { + number: 76, + }, + function: 'handle_request', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'puma/server.rb', + abs_path: '/usr/local/bundle/gems/puma-5.5.0/lib/puma/server.rb', + line: { + number: 447, + }, + function: 'process_client', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'puma/thread_pool.rb', + abs_path: + '/usr/local/bundle/gems/puma-5.5.0/lib/puma/thread_pool.rb', + line: { + number: 147, + }, + function: 'block in spawn_thread', + }, + ], + subtype: 'http', + destination: { + service: { + resource: 'opbeans:3000', + name: 'http', + type: 'external', + }, + }, + name: 'GET opbeans', + http: { + method: 'GET', + response: { + status_code: 500, + }, + }, + 'http.url.original': 'http://opbeans:3000/api/products/3', + id: '4eeaa6dfbfd047cd', + type: 'external', + }, + }, + { + parent: { + id: '9f50f43e924d0b46', + }, + agent: { + name: 'go', + version: '1.14.0', + }, + destination: { + address: 'opbeans', + port: 3000, + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-lmf6c', + id: '7eedab18-1171-4a1b-a590-975e13fd103a', + ephemeral_id: '90034868-48e6-418c-8ab4-6616b403bca7', + type: 'apm-server', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + ecs: { + version: '1.11.0', + }, + event: { + outcome: 'failure', + }, + timestamp: { + us: 1634651822536408, + }, + processor: { + name: 'transaction', + event: 'span', + }, + url: { + original: 'http://opbeans:3000/api/products/3', + }, + '@timestamp': '2021-10-19T13:57:02.536Z', + service: { + environment: 'testing', + name: 'opbeans-go', + }, + http: { + response: { + status_code: 500, + }, + }, + transaction: { + id: '9f50f43e924d0b46', + }, + span: { + duration: { + us: 13359, + }, + stacktrace: [ + { + exclude_from_grouping: false, + library_frame: true, + filename: 'span.go', + abs_path: '/go/pkg/mod/go.elastic.co/apm@v1.14.0/span.go', + line: { + number: 334, + }, + module: 'go.elastic.co/apm', + function: '(*Span).End', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'client.go', + abs_path: + '/go/pkg/mod/go.elastic.co/apm/module/apmhttp@v1.14.0/client.go', + line: { + number: 198, + }, + module: 'go.elastic.co/apm/module/apmhttp', + function: '(*responseBody).endSpan', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'client.go', + abs_path: + '/go/pkg/mod/go.elastic.co/apm/module/apmhttp@v1.14.0/client.go', + line: { + number: 187, + }, + function: '(*responseBody).Read', + module: 'go.elastic.co/apm/module/apmhttp', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'reverseproxy.go', + abs_path: '/usr/local/go/src/net/http/httputil/reverseproxy.go', + line: { + number: 461, + }, + module: 'net/http/httputil', + function: '(*ReverseProxy).copyBuffer', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'reverseproxy.go', + abs_path: '/usr/local/go/src/net/http/httputil/reverseproxy.go', + line: { + number: 449, + }, + module: 'net/http/httputil', + function: '(*ReverseProxy).copyResponse', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'reverseproxy.go', + abs_path: '/usr/local/go/src/net/http/httputil/reverseproxy.go', + line: { + number: 338, + }, + module: 'net/http/httputil', + function: '(*ReverseProxy).ServeHTTP', + }, + { + exclude_from_grouping: false, + filename: 'main.go', + abs_path: '/src/opbeans-go/main.go', + line: { + number: 196, + }, + module: 'main', + function: 'Main.func2', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go', + filename: 'context.go', + line: { + number: 165, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Context).Next', + }, + { + exclude_from_grouping: false, + filename: 'main.go', + abs_path: '/src/opbeans-go/main.go', + line: { + number: 174, + }, + module: 'main', + function: 'Main.func1', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'context.go', + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go', + line: { + number: 165, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Context).Next', + }, + { + exclude_from_grouping: false, + filename: 'logger.go', + abs_path: '/src/opbeans-go/logger.go', + line: { + number: 36, + }, + module: 'main', + function: 'logrusMiddleware', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'context.go', + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go', + line: { + number: 165, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Context).Next', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'middleware.go', + abs_path: + '/go/pkg/mod/go.elastic.co/apm/module/apmgin@v1.14.0/middleware.go', + line: { + number: 98, + }, + module: 'go.elastic.co/apm/module/apmgin', + function: '(*middleware).handle', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'context.go', + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go', + line: { + number: 165, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Context).Next', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'cache.go', + abs_path: + '/go/pkg/mod/github.com/gin-contrib/cache@v1.1.0/cache.go', + line: { + number: 128, + }, + module: 'github.com/gin-contrib/cache', + function: 'Cache.func1', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'context.go', + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go', + line: { + number: 165, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Context).Next', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'gin.go', + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go', + line: { + number: 489, + }, + function: '(*Engine).handleHTTPRequest', + module: 'github.com/gin-gonic/gin', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'gin.go', + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go', + line: { + number: 445, + }, + function: '(*Engine).ServeHTTP', + module: 'github.com/gin-gonic/gin', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'server.go', + abs_path: '/usr/local/go/src/net/http/server.go', + line: { + number: 2878, + }, + module: 'net/http', + function: 'serverHandler.ServeHTTP', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'server.go', + abs_path: '/usr/local/go/src/net/http/server.go', + line: { + number: 1929, + }, + module: 'net/http', + function: '(*conn).serve', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'asm_amd64.s', + abs_path: '/usr/local/go/src/runtime/asm_amd64.s', + line: { + number: 1581, + }, + module: 'runtime', + function: 'goexit', + }, + ], + subtype: 'http', + name: 'GET opbeans:3000', + destination: { + service: { + resource: 'opbeans:3000', + name: 'http://opbeans:3000', + type: 'external', + }, + }, + http: { + response: { + status_code: 500, + }, + }, + 'http.url.original': 'http://opbeans:3000/api/products/3', + id: '8d099ab4fcec4ab9', + type: 'external', + }, + }, + { + parent: { + id: '86c43ac014573747', + }, + agent: { + name: 'go', + version: '1.14.0', + }, + processor: { + name: 'transaction', + event: 'span', + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-lmf6c', + id: '7eedab18-1171-4a1b-a590-975e13fd103a', + type: 'apm-server', + ephemeral_id: '90034868-48e6-418c-8ab4-6616b403bca7', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + '@timestamp': '2021-10-19T13:57:02.539Z', + ecs: { + version: '1.11.0', + }, + service: { + environment: 'testing', + name: 'opbeans-go', + }, + event: { + outcome: 'unknown', + }, + transaction: { + id: '9f50f43e924d0b46', + }, + span: { + duration: { + us: 13359, + }, + stacktrace: [ + { + exclude_from_grouping: false, + library_frame: true, + filename: 'span.go', + abs_path: '/go/pkg/mod/go.elastic.co/apm@v1.14.0/span.go', + line: { + number: 334, + }, + module: 'go.elastic.co/apm', + function: '(*Span).End', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'clienttrace.go', + abs_path: + '/go/pkg/mod/go.elastic.co/apm/module/apmhttp@v1.14.0/clienttrace.go', + line: { + number: 130, + }, + module: 'go.elastic.co/apm/module/apmhttp', + function: 'withClientTrace.func8', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'transport.go', + abs_path: '/usr/local/go/src/net/http/transport.go', + line: { + number: 2272, + }, + function: '(*persistConn).readResponse', + module: 'net/http', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'transport.go', + abs_path: '/usr/local/go/src/net/http/transport.go', + line: { + number: 2102, + }, + function: '(*persistConn).readLoop', + module: 'net/http', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'asm_amd64.s', + abs_path: '/usr/local/go/src/runtime/asm_amd64.s', + line: { + number: 1581, + }, + module: 'runtime', + function: 'goexit', + }, + ], + subtype: 'http', + name: 'Request', + action: 'request', + id: '997cdcc26a60d0ad', + type: 'external', + }, + timestamp: { + us: 1634651822536408, + }, + }, + { + parent: { + id: 'b7801be83bcdc972', + }, + agent: { + name: 'go', + version: '1.14.0', + }, + destination: { + address: 'opbeans', + port: 3000, + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-zvs6p', + id: '69a7066f-46d2-42c4-a4cc-8400f60bf2b5', + ephemeral_id: '0ab88569-c301-40e9-8e78-cac7c1dac2bc', + type: 'apm-server', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + ecs: { + version: '1.11.0', + }, + event: { + outcome: 'failure', + }, + timestamp: { + us: 1634651822536408, + }, + processor: { + name: 'transaction', + event: 'span', + }, + url: { + original: 'http://opbeans:3000/api/products/3', + }, + '@timestamp': '2021-10-19T13:57:02.539Z', + service: { + environment: 'testing', + name: 'opbeans-go', + }, + http: { + response: { + status_code: 500, + }, + }, + transaction: { + id: 'b7801be83bcdc972', + }, + span: { + duration: { + us: 13359, + }, + stacktrace: [ + { + library_frame: true, + exclude_from_grouping: false, + filename: 'span.go', + abs_path: '/go/pkg/mod/go.elastic.co/apm@v1.14.0/span.go', + line: { + number: 334, + }, + module: 'go.elastic.co/apm', + function: '(*Span).End', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'client.go', + abs_path: + '/go/pkg/mod/go.elastic.co/apm/module/apmhttp@v1.14.0/client.go', + line: { + number: 198, + }, + module: 'go.elastic.co/apm/module/apmhttp', + function: '(*responseBody).endSpan', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'client.go', + abs_path: + '/go/pkg/mod/go.elastic.co/apm/module/apmhttp@v1.14.0/client.go', + line: { + number: 187, + }, + module: 'go.elastic.co/apm/module/apmhttp', + function: '(*responseBody).Read', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'reverseproxy.go', + abs_path: '/usr/local/go/src/net/http/httputil/reverseproxy.go', + line: { + number: 461, + }, + module: 'net/http/httputil', + function: '(*ReverseProxy).copyBuffer', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'reverseproxy.go', + abs_path: '/usr/local/go/src/net/http/httputil/reverseproxy.go', + line: { + number: 449, + }, + module: 'net/http/httputil', + function: '(*ReverseProxy).copyResponse', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'reverseproxy.go', + abs_path: '/usr/local/go/src/net/http/httputil/reverseproxy.go', + line: { + number: 338, + }, + module: 'net/http/httputil', + function: '(*ReverseProxy).ServeHTTP', + }, + { + exclude_from_grouping: false, + filename: 'main.go', + abs_path: '/src/opbeans-go/main.go', + line: { + number: 196, + }, + module: 'main', + function: 'Main.func2', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'context.go', + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go', + line: { + number: 165, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Context).Next', + }, + { + exclude_from_grouping: false, + filename: 'main.go', + abs_path: '/src/opbeans-go/main.go', + line: { + number: 174, + }, + module: 'main', + function: 'Main.func1', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'context.go', + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go', + line: { + number: 165, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Context).Next', + }, + { + exclude_from_grouping: false, + filename: 'logger.go', + abs_path: '/src/opbeans-go/logger.go', + line: { + number: 36, + }, + module: 'main', + function: 'logrusMiddleware', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'context.go', + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go', + line: { + number: 165, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Context).Next', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'middleware.go', + abs_path: + '/go/pkg/mod/go.elastic.co/apm/module/apmgin@v1.14.0/middleware.go', + line: { + number: 98, + }, + function: '(*middleware).handle', + module: 'go.elastic.co/apm/module/apmgin', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'context.go', + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go', + line: { + number: 165, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Context).Next', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: + '/go/pkg/mod/github.com/gin-contrib/cache@v1.1.0/cache.go', + filename: 'cache.go', + line: { + number: 128, + }, + module: 'github.com/gin-contrib/cache', + function: 'Cache.func1', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'context.go', + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go', + line: { + number: 165, + }, + function: '(*Context).Next', + module: 'github.com/gin-gonic/gin', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'gin.go', + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go', + line: { + number: 489, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Engine).handleHTTPRequest', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'gin.go', + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go', + line: { + number: 445, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Engine).ServeHTTP', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: '/usr/local/go/src/net/http/server.go', + filename: 'server.go', + line: { + number: 2878, + }, + module: 'net/http', + function: 'serverHandler.ServeHTTP', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: '/usr/local/go/src/net/http/server.go', + filename: 'server.go', + line: { + number: 1929, + }, + module: 'net/http', + function: '(*conn).serve', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'asm_amd64.s', + abs_path: '/usr/local/go/src/runtime/asm_amd64.s', + line: { + number: 1581, + }, + module: 'runtime', + function: 'goexit', + }, + ], + subtype: 'http', + name: 'GET opbeans:3000', + destination: { + service: { + resource: 'opbeans:3000', + name: 'http://opbeans:3000', + type: 'external', + }, + }, + http: { + response: { + status_code: 500, + }, + }, + 'http.url.original': 'http://opbeans:3000/api/products/3', + id: '35e3637e26919055', + type: 'external', + }, + }, + { + parent: { + id: '84749ec73b1268b3', + }, + agent: { + name: 'go', + version: '1.14.0', + }, + processor: { + name: 'transaction', + event: 'span', + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-zvs6p', + id: '69a7066f-46d2-42c4-a4cc-8400f60bf2b5', + type: 'apm-server', + ephemeral_id: '0ab88569-c301-40e9-8e78-cac7c1dac2bc', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + '@timestamp': '2021-10-19T13:57:02.539Z', + ecs: { + version: '1.11.0', + }, + service: { + environment: 'testing', + name: 'opbeans-go', + }, + event: { + outcome: 'unknown', + }, + transaction: { + id: 'b7801be83bcdc972', + }, + span: { + duration: { + us: 13359, + }, + stacktrace: [ + { + library_frame: true, + exclude_from_grouping: false, + filename: 'span.go', + abs_path: '/go/pkg/mod/go.elastic.co/apm@v1.14.0/span.go', + line: { + number: 334, + }, + module: 'go.elastic.co/apm', + function: '(*Span).End', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'clienttrace.go', + abs_path: + '/go/pkg/mod/go.elastic.co/apm/module/apmhttp@v1.14.0/clienttrace.go', + line: { + number: 130, + }, + module: 'go.elastic.co/apm/module/apmhttp', + function: 'withClientTrace.func8', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'transport.go', + abs_path: '/usr/local/go/src/net/http/transport.go', + line: { + number: 2272, + }, + module: 'net/http', + function: '(*persistConn).readResponse', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'transport.go', + abs_path: '/usr/local/go/src/net/http/transport.go', + line: { + number: 2102, + }, + module: 'net/http', + function: '(*persistConn).readLoop', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'asm_amd64.s', + abs_path: '/usr/local/go/src/runtime/asm_amd64.s', + line: { + number: 1581, + }, + module: 'runtime', + function: 'goexit', + }, + ], + subtype: 'http', + name: 'Request', + action: 'request', + id: 'a9b4d44c3d699cbb', + type: 'external', + }, + timestamp: { + us: 1634651822536408, + }, + }, + { + parent: { + id: '2c30263c4ad8fe8b', + }, + agent: { + name: 'java', + ephemeral_id: '75e36588-9adb-4bb0-bfee-a333b1c57e67', + version: 'unknown', + }, + processor: { + name: 'transaction', + event: 'span', + }, + labels: { + productId: '3', + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-lmf6c', + id: '7eedab18-1171-4a1b-a590-975e13fd103a', + ephemeral_id: '90034868-48e6-418c-8ab4-6616b403bca7', + type: 'apm-server', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + '@timestamp': '2021-10-19T13:57:02.540Z', + ecs: { + version: '1.11.0', + }, + service: { + environment: 'production', + name: 'opbeans-java', + }, + event: { + outcome: 'success', + }, + transaction: { + id: '2c30263c4ad8fe8b', + }, + span: { + duration: { + us: 13359, + }, + name: 'OpenTracing product span', + id: 'd22c1e48b2489017', + type: 'custom', + }, + timestamp: { + us: 1634651822536408, + }, + }, + { + parent: { + id: 'd22c1e48b2489017', + }, + agent: { + name: 'java', + ephemeral_id: '75e36588-9adb-4bb0-bfee-a333b1c57e67', + version: 'unknown', + }, + destination: { + address: 'db-postgresql', + port: 5432, + }, + processor: { + name: 'transaction', + event: 'span', + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-lmf6c', + id: '7eedab18-1171-4a1b-a590-975e13fd103a', + type: 'apm-server', + ephemeral_id: '90034868-48e6-418c-8ab4-6616b403bca7', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + '@timestamp': '2021-10-19T13:57:02.542Z', + ecs: { + version: '1.11.0', + }, + service: { + environment: 'production', + name: 'opbeans-java', + }, + event: { + outcome: 'success', + }, + transaction: { + id: '2c30263c4ad8fe8b', + }, + timestamp: { + us: 1634651822536408, + }, + span: { + duration: { + us: 13359, + }, + subtype: 'postgresql', + destination: { + service: { + resource: 'postgresql', + }, + }, + name: 'SELECT FROM products', + action: 'query', + id: '3851260ca4365f9e', + type: 'db', + db: { + instance: 'opbeans-java', + statement: + 'select product0_.id as col_0_0_, product0_.sku as col_1_0_, product0_.name as col_2_0_, product0_.description as col_3_0_, product0_.cost as col_4_0_, product0_.selling_price as col_5_0_, product0_.stock as col_6_0_, producttyp1_.id as col_7_0_, producttyp1_.name as col_8_0_, (select sum(orderline2_.amount) from order_lines orderline2_ where orderline2_.product_id=product0_.id) as col_9_0_ from products product0_ left outer join product_types producttyp1_ on product0_.type_id=producttyp1_.id where product0_.id=?', + type: 'sql', + user: { + name: 'elastic', + }, + }, + }, + }, + { + parent: { + id: '3851260ca4365f9e', + }, + agent: { + name: 'java', + ephemeral_id: '75e36588-9adb-4bb0-bfee-a333b1c57e67', + version: 'unknown', + }, + destination: { + address: 'db-postgresql', + port: 5432, + }, + processor: { + name: 'transaction', + event: 'span', + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-lmf6c', + id: '7eedab18-1171-4a1b-a590-975e13fd103a', + type: 'apm-server', + ephemeral_id: '90034868-48e6-418c-8ab4-6616b403bca7', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + '@timestamp': '2021-10-19T13:57:02.541Z', + ecs: { + version: '1.11.0', + }, + service: { + environment: 'production', + name: 'opbeans-java', + }, + event: { + outcome: 'success', + }, + transaction: { + id: '2c30263c4ad8fe8b', + }, + span: { + duration: { + us: 13359, + }, + subtype: 'postgresql', + destination: { + service: { + resource: 'postgresql', + }, + }, + name: 'empty query', + action: 'query', + id: '86c43ac014573747', + type: 'db', + db: { + rows_affected: 0, + instance: 'opbeans-java', + statement: '(empty query)', + type: 'sql', + user: { + name: 'elastic', + }, + }, + }, + timestamp: { + us: 1634651822536408, + }, + }, + { + parent: { + id: '997cdcc26a60d0ad', + }, + agent: { + name: 'go', + version: '1.14.0', + }, + processor: { + name: 'transaction', + event: 'span', + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-lmf6c', + id: '7eedab18-1171-4a1b-a590-975e13fd103a', + type: 'apm-server', + ephemeral_id: '90034868-48e6-418c-8ab4-6616b403bca7', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + '@timestamp': '2021-10-19T13:57:02.548Z', + ecs: { + version: '1.11.0', + }, + service: { + environment: 'testing', + name: 'opbeans-go', + }, + event: { + outcome: 'unknown', + }, + transaction: { + id: '9f50f43e924d0b46', + }, + timestamp: { + us: 1634651822536408, + }, + span: { + duration: { + us: 13359, + }, + subtype: 'http', + name: 'Response', + action: 'response', + id: '84749ec73b1268b3', + type: 'external', + }, + }, + { + parent: { + id: 'a9b4d44c3d699cbb', + }, + agent: { + name: 'go', + version: '1.14.0', + }, + processor: { + name: 'transaction', + event: 'span', + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-zvs6p', + id: '69a7066f-46d2-42c4-a4cc-8400f60bf2b5', + type: 'apm-server', + ephemeral_id: '0ab88569-c301-40e9-8e78-cac7c1dac2bc', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + '@timestamp': '2021-10-19T13:57:02.547Z', + ecs: { + version: '1.11.0', + }, + service: { + environment: 'testing', + name: 'opbeans-go', + }, + event: { + outcome: 'unknown', + }, + transaction: { + id: 'b7801be83bcdc972', + }, + span: { + duration: { + us: 13359, + }, + subtype: 'http', + name: 'Response', + action: 'response', + id: '04991f3b9d3696c5', + type: 'external', + }, + timestamp: { + us: 1634651822536408, + }, + }, + ], + errorDocs: [ + { + container: { + id: '59036ecb70908dfec4e03edc477f6875d08677871b4af0db3144373802d00cb1', + }, + kubernetes: { + pod: { + uid: '878bab2a-1309-44ae-a0e2-c98a0b187da1', + name: 'opbeans-java-5f45d77dd8-h8bnb', + }, + }, + parent: { + id: '2c30263c4ad8fe8b', + }, + agent: { + name: 'java', + ephemeral_id: '75e36588-9adb-4bb0-bfee-a333b1c57e67', + version: 'unknown', + }, + source: { + ip: '10.12.0.13', + }, + error: { + exception: [ + { + stacktrace: [ + { + exclude_from_grouping: false, + library_frame: true, + filename: 'AbstractMessageConverterMethodProcessor.java', + classname: + 'org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor', + line: { + number: 226, + }, + module: 'org.springframework.web.servlet.mvc.method.annotation', + function: 'writeWithMessageConverters', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'RequestResponseBodyMethodProcessor.java', + classname: + 'org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor', + line: { + number: 180, + }, + module: 'org.springframework.web.servlet.mvc.method.annotation', + function: 'handleReturnValue', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'HandlerMethodReturnValueHandlerComposite.java', + classname: + 'org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite', + line: { + number: 82, + }, + module: 'org.springframework.web.method.support', + function: 'handleReturnValue', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'ServletInvocableHandlerMethod.java', + classname: + 'org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod', + line: { + number: 119, + }, + module: 'org.springframework.web.servlet.mvc.method.annotation', + function: 'invokeAndHandle', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'RequestMappingHandlerAdapter.java', + classname: + 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter', + line: { + number: 877, + }, + function: 'invokeHandlerMethod', + module: 'org.springframework.web.servlet.mvc.method.annotation', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'RequestMappingHandlerAdapter.java', + classname: + 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter', + line: { + number: 783, + }, + module: 'org.springframework.web.servlet.mvc.method.annotation', + function: 'handleInternal', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'AbstractHandlerMethodAdapter.java', + classname: + 'org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter', + line: { + number: 87, + }, + module: 'org.springframework.web.servlet.mvc.method', + function: 'handle', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'DispatcherServlet.java', + classname: 'org.springframework.web.servlet.DispatcherServlet', + line: { + number: 991, + }, + function: 'doDispatch', + module: 'org.springframework.web.servlet', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'DispatcherServlet.java', + classname: 'org.springframework.web.servlet.DispatcherServlet', + line: { + number: 925, + }, + module: 'org.springframework.web.servlet', + function: 'doService', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'FrameworkServlet.java', + classname: 'org.springframework.web.servlet.FrameworkServlet', + line: { + number: 974, + }, + module: 'org.springframework.web.servlet', + function: 'processRequest', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'FrameworkServlet.java', + classname: 'org.springframework.web.servlet.FrameworkServlet', + line: { + number: 866, + }, + module: 'org.springframework.web.servlet', + function: 'doGet', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'HttpServlet.java', + classname: 'javax.servlet.http.HttpServlet', + line: { + number: 635, + }, + module: 'javax.servlet.http', + function: 'service', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'FrameworkServlet.java', + classname: 'org.springframework.web.servlet.FrameworkServlet', + line: { + number: 851, + }, + function: 'service', + module: 'org.springframework.web.servlet', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'HttpServlet.java', + classname: 'javax.servlet.http.HttpServlet', + line: { + number: 742, + }, + module: 'javax.servlet.http', + function: 'service', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'ApplicationFilterChain.java', + classname: 'org.apache.catalina.core.ApplicationFilterChain', + line: { + number: 231, + }, + module: 'org.apache.catalina.core', + function: 'internalDoFilter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'ApplicationFilterChain.java', + classname: 'org.apache.catalina.core.ApplicationFilterChain', + line: { + number: 166, + }, + module: 'org.apache.catalina.core', + function: 'doFilter', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'WsFilter.java', + classname: 'org.apache.tomcat.websocket.server.WsFilter', + line: { + number: 52, + }, + module: 'org.apache.tomcat.websocket.server', + function: 'doFilter', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'ApplicationFilterChain.java', + classname: 'org.apache.catalina.core.ApplicationFilterChain', + line: { + number: 193, + }, + module: 'org.apache.catalina.core', + function: 'internalDoFilter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'ApplicationFilterChain.java', + classname: 'org.apache.catalina.core.ApplicationFilterChain', + line: { + number: 166, + }, + module: 'org.apache.catalina.core', + function: 'doFilter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'RequestContextFilter.java', + classname: + 'org.springframework.web.filter.RequestContextFilter', + line: { + number: 99, + }, + function: 'doFilterInternal', + module: 'org.springframework.web.filter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'OncePerRequestFilter.java', + classname: + 'org.springframework.web.filter.OncePerRequestFilter', + line: { + number: 107, + }, + module: 'org.springframework.web.filter', + function: 'doFilter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'ApplicationFilterChain.java', + classname: 'org.apache.catalina.core.ApplicationFilterChain', + line: { + number: 193, + }, + module: 'org.apache.catalina.core', + function: 'internalDoFilter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'ApplicationFilterChain.java', + classname: 'org.apache.catalina.core.ApplicationFilterChain', + line: { + number: 166, + }, + module: 'org.apache.catalina.core', + function: 'doFilter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'HttpPutFormContentFilter.java', + classname: + 'org.springframework.web.filter.HttpPutFormContentFilter', + line: { + number: 109, + }, + function: 'doFilterInternal', + module: 'org.springframework.web.filter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'OncePerRequestFilter.java', + classname: + 'org.springframework.web.filter.OncePerRequestFilter', + line: { + number: 107, + }, + module: 'org.springframework.web.filter', + function: 'doFilter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'ApplicationFilterChain.java', + classname: 'org.apache.catalina.core.ApplicationFilterChain', + line: { + number: 193, + }, + module: 'org.apache.catalina.core', + function: 'internalDoFilter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'ApplicationFilterChain.java', + classname: 'org.apache.catalina.core.ApplicationFilterChain', + line: { + number: 166, + }, + module: 'org.apache.catalina.core', + function: 'doFilter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'HiddenHttpMethodFilter.java', + classname: + 'org.springframework.web.filter.HiddenHttpMethodFilter', + line: { + number: 81, + }, + function: 'doFilterInternal', + module: 'org.springframework.web.filter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'OncePerRequestFilter.java', + classname: + 'org.springframework.web.filter.OncePerRequestFilter', + line: { + number: 107, + }, + module: 'org.springframework.web.filter', + function: 'doFilter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'ApplicationFilterChain.java', + classname: 'org.apache.catalina.core.ApplicationFilterChain', + line: { + number: 193, + }, + module: 'org.apache.catalina.core', + function: 'internalDoFilter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'ApplicationFilterChain.java', + classname: 'org.apache.catalina.core.ApplicationFilterChain', + line: { + number: 166, + }, + module: 'org.apache.catalina.core', + function: 'doFilter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'CharacterEncodingFilter.java', + classname: + 'org.springframework.web.filter.CharacterEncodingFilter', + line: { + number: 200, + }, + module: 'org.springframework.web.filter', + function: 'doFilterInternal', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'OncePerRequestFilter.java', + classname: + 'org.springframework.web.filter.OncePerRequestFilter', + line: { + number: 107, + }, + module: 'org.springframework.web.filter', + function: 'doFilter', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'ApplicationFilterChain.java', + classname: 'org.apache.catalina.core.ApplicationFilterChain', + line: { + number: 193, + }, + module: 'org.apache.catalina.core', + function: 'internalDoFilter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'ApplicationFilterChain.java', + classname: 'org.apache.catalina.core.ApplicationFilterChain', + line: { + number: 166, + }, + module: 'org.apache.catalina.core', + function: 'doFilter', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'StandardWrapperValve.java', + classname: 'org.apache.catalina.core.StandardWrapperValve', + line: { + number: 198, + }, + module: 'org.apache.catalina.core', + function: 'invoke', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'StandardContextValve.java', + classname: 'org.apache.catalina.core.StandardContextValve', + line: { + number: 96, + }, + module: 'org.apache.catalina.core', + function: 'invoke', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'AuthenticatorBase.java', + classname: + 'org.apache.catalina.authenticator.AuthenticatorBase', + line: { + number: 496, + }, + module: 'org.apache.catalina.authenticator', + function: 'invoke', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'StandardHostValve.java', + classname: 'org.apache.catalina.core.StandardHostValve', + line: { + number: 140, + }, + module: 'org.apache.catalina.core', + function: 'invoke', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'ErrorReportValve.java', + classname: 'org.apache.catalina.valves.ErrorReportValve', + line: { + number: 81, + }, + module: 'org.apache.catalina.valves', + function: 'invoke', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'StandardEngineValve.java', + classname: 'org.apache.catalina.core.StandardEngineValve', + line: { + number: 87, + }, + module: 'org.apache.catalina.core', + function: 'invoke', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'CoyoteAdapter.java', + classname: 'org.apache.catalina.connector.CoyoteAdapter', + line: { + number: 342, + }, + module: 'org.apache.catalina.connector', + function: 'service', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'Http11Processor.java', + classname: 'org.apache.coyote.http11.Http11Processor', + line: { + number: 803, + }, + module: 'org.apache.coyote.http11', + function: 'service', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'AbstractProcessorLight.java', + classname: 'org.apache.coyote.AbstractProcessorLight', + line: { + number: 66, + }, + module: 'org.apache.coyote', + function: 'process', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'AbstractProtocol.java', + classname: + 'org.apache.coyote.AbstractProtocol$ConnectionHandler', + line: { + number: 790, + }, + module: 'org.apache.coyote', + function: 'process', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'NioEndpoint.java', + classname: + 'org.apache.tomcat.util.net.NioEndpoint$SocketProcessor', + line: { + number: 1468, + }, + module: 'org.apache.tomcat.util.net', + function: 'doRun', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'SocketProcessorBase.java', + classname: 'org.apache.tomcat.util.net.SocketProcessorBase', + line: { + number: 49, + }, + module: 'org.apache.tomcat.util.net', + function: 'run', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'TaskThread.java', + classname: + 'org.apache.tomcat.util.threads.TaskThread$WrappingRunnable', + line: { + number: 61, + }, + module: 'org.apache.tomcat.util.threads', + function: 'run', + }, + ], + message: + 'No converter found for return value of type: class com.sun.proxy.$Proxy158', + type: 'org.springframework.http.converter.HttpMessageNotWritableException', + }, + ], + id: '128f8ecf47bc8a800269ee6e5ac90008', + grouping_key: 'cc9272d7511c88a533ac41cc3e2ce54b', + grouping_name: + 'No converter found for return value of type: class com.sun.proxy.$Proxy158', + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-lmf6c', + id: '7eedab18-1171-4a1b-a590-975e13fd103a', + ephemeral_id: '90034868-48e6-418c-8ab4-6616b403bca7', + type: 'apm-server', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + ecs: { + version: '1.11.0', + }, + host: { + os: { + platform: 'Linux', + }, + ip: '10.12.0.15', + architecture: 'amd64', + }, + client: { + ip: '10.12.0.22', + }, + event: { + ingested: '2021-10-19T13:57:10.382394342Z', + }, + user_agent: { + original: 'http.rb/5.0.2', + name: 'Other', + device: { + name: 'Generic Feature Phone', + type: 'Other', + }, + }, + timestamp: { + us: 1634651822536408, + }, + process: { + pid: 7, + title: '/opt/java/openjdk/bin/java', + ppid: 1, + }, + message: + 'No converter found for return value of type: class com.sun.proxy.$Proxy158', + processor: { + name: 'error', + event: 'error', + }, + url: { + path: '/api/products/3', + scheme: 'http', + port: 3000, + domain: 'opbeans', + full: 'http://opbeans:3000/api/products/3', + }, + '@timestamp': '2021-10-19T13:57:02.546Z', + service: { + node: { + name: '59036ecb70908dfec4e03edc477f6875d08677871b4af0db3144373802d00cb1', + }, + environment: 'production', + name: 'opbeans-java', + runtime: { + name: 'Java', + version: '11.0.11', + }, + language: { + name: 'Java', + version: '11.0.11', + }, + version: '2021-10-14 17:49:52', + }, + http: { + request: { + headers: { + 'User-Agent': ['http.rb/5.0.2'], + 'X-Forwarded-For': ['10.12.0.22, 10.12.0.14'], + Host: ['opbeans:3000'], + 'Accept-Encoding': ['gzip'], + 'Elastic-Apm-Traceparent': [ + '00-d5e80ae688f1fef91533f02dd2bdc769-35e3637e26919055-01', + ], + Tracestate: ['es=s:1.0'], + Traceparent: [ + '00-d5e80ae688f1fef91533f02dd2bdc769-35e3637e26919055-01', + ], + }, + method: 'GET', + }, + response: { + status_code: 500, + finished: true, + headers_sent: true, + }, + version: '1.1', + }, + transaction: { + id: '2c30263c4ad8fe8b', + type: 'request', + sampled: true, + }, + }, + { + container: { + id: 'e7b69f99cb7523bedea6d7c97b684cf4b7ff458d0cba1efb1ac843300b3bf3c7', + }, + kubernetes: { + pod: { + uid: 'c5169b50-f3b3-4693-8e4b-150fca17c333', + name: 'opbeans-go-5d795ddf6b-rhlvf', + }, + }, + parent: { + id: '9f50f43e924d0b46', + }, + agent: { + name: 'go', + version: '1.14.0', + }, + process: { + args: [ + '/opbeans-go', + '-log-level=debug', + '-log-json', + '-listen=:3000', + '-frontend=/opbeans-frontend', + '-db=postgres:', + '-cache=redis://redis-master:6379', + ], + pid: 1, + title: 'opbeans-go', + ppid: 0, + }, + error: { + culprit: 'logrusMiddleware', + log: { + stacktrace: [ + { + library_frame: true, + exclude_from_grouping: false, + filename: 'hook.go', + abs_path: + '/go/pkg/mod/go.elastic.co/apm/module/apmlogrus@v1.14.0/hook.go', + line: { + number: 102, + }, + module: 'go.elastic.co/apm/module/apmlogrus', + function: '(*Hook).Fire', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'hooks.go', + abs_path: + '/go/pkg/mod/github.com/sirupsen/logrus@v1.8.1/hooks.go', + line: { + number: 28, + }, + module: 'github.com/sirupsen/logrus', + function: 'LevelHooks.Fire', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: + '/go/pkg/mod/github.com/sirupsen/logrus@v1.8.1/entry.go', + filename: 'entry.go', + line: { + number: 272, + }, + module: 'github.com/sirupsen/logrus', + function: '(*Entry).fireHooks', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'entry.go', + abs_path: + '/go/pkg/mod/github.com/sirupsen/logrus@v1.8.1/entry.go', + line: { + number: 241, + }, + module: 'github.com/sirupsen/logrus', + function: '(*Entry).log', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'entry.go', + abs_path: + '/go/pkg/mod/github.com/sirupsen/logrus@v1.8.1/entry.go', + line: { + number: 293, + }, + module: 'github.com/sirupsen/logrus', + function: '(*Entry).Log', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'entry.go', + abs_path: + '/go/pkg/mod/github.com/sirupsen/logrus@v1.8.1/entry.go', + line: { + number: 338, + }, + module: 'github.com/sirupsen/logrus', + function: '(*Entry).Logf', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'entry.go', + abs_path: + '/go/pkg/mod/github.com/sirupsen/logrus@v1.8.1/entry.go', + line: { + number: 367, + }, + function: '(*Entry).Errorf', + module: 'github.com/sirupsen/logrus', + }, + { + exclude_from_grouping: false, + filename: 'logger.go', + abs_path: '/src/opbeans-go/logger.go', + line: { + number: 56, + }, + module: 'main', + function: 'logrusMiddleware', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: + '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go', + filename: 'context.go', + line: { + number: 165, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Context).Next', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: + '/go/pkg/mod/go.elastic.co/apm/module/apmgin@v1.14.0/middleware.go', + filename: 'middleware.go', + line: { + number: 98, + }, + module: 'go.elastic.co/apm/module/apmgin', + function: '(*middleware).handle', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'context.go', + abs_path: + '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go', + line: { + number: 165, + }, + function: '(*Context).Next', + module: 'github.com/gin-gonic/gin', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'cache.go', + abs_path: + '/go/pkg/mod/github.com/gin-contrib/cache@v1.1.0/cache.go', + line: { + number: 128, + }, + module: 'github.com/gin-contrib/cache', + function: 'Cache.func1', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'context.go', + abs_path: + '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go', + line: { + number: 165, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Context).Next', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'gin.go', + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go', + line: { + number: 489, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Engine).handleHTTPRequest', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go', + filename: 'gin.go', + line: { + number: 445, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Engine).ServeHTTP', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'server.go', + abs_path: '/usr/local/go/src/net/http/server.go', + line: { + number: 2878, + }, + module: 'net/http', + function: 'serverHandler.ServeHTTP', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'server.go', + abs_path: '/usr/local/go/src/net/http/server.go', + line: { + number: 1929, + }, + module: 'net/http', + function: '(*conn).serve', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'asm_amd64.s', + abs_path: '/usr/local/go/src/runtime/asm_amd64.s', + line: { + number: 1581, + }, + module: 'runtime', + function: 'goexit', + }, + ], + level: 'error', + message: 'GET /api/products/3 (500)', + }, + id: '1660f82c1340f415e9a31b47565300ee', + grouping_key: '7a640436a9be648fd708703d1ac84650', + grouping_name: 'GET /api/products/3 (500)', + }, + message: 'GET /api/products/3 (500)', + processor: { + name: 'error', + event: 'error', + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-lmf6c', + id: '7eedab18-1171-4a1b-a590-975e13fd103a', + ephemeral_id: '90034868-48e6-418c-8ab4-6616b403bca7', + type: 'apm-server', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + '@timestamp': '2021-10-19T13:57:02.539Z', + ecs: { + version: '1.11.0', + }, + service: { + node: { + name: 'e7b69f99cb7523bedea6d7c97b684cf4b7ff458d0cba1efb1ac843300b3bf3c7', + }, + environment: 'testing', + name: 'opbeans-go', + runtime: { + name: 'gc', + version: 'go1.17.2', + }, + language: { + name: 'go', + version: 'go1.17.2', + }, + version: '2021-10-14 17:49:50', + }, + host: { + os: { + platform: 'linux', + }, + ip: '10.12.0.14', + architecture: 'amd64', + }, + event: { + ingested: '2021-10-19T13:57:05.412811279Z', + }, + transaction: { + id: '9f50f43e924d0b46', + }, + timestamp: { + us: 1634651822536408, + }, + }, + { + container: { + id: '015d1127421e2c3d42a0fb031fc75e989813f58973143b6c7e33dca6ccc6f31b', + }, + kubernetes: { + pod: { + uid: '459a6abf-198e-4107-b4dd-b0ae826755ab', + name: 'opbeans-go-nsn-69b89c4598-xsvgh', + }, + }, + parent: { + id: 'b7801be83bcdc972', + }, + process: { + args: [ + '/opbeans-go', + '-log-level=debug', + '-log-json', + '-listen=:3000', + '-frontend=/opbeans-frontend', + '-db=postgres:', + '-cache=redis://redis-master:6379', + ], + pid: 1, + title: 'opbeans-go', + ppid: 0, + }, + agent: { + name: 'go', + version: '1.14.0', + }, + message: 'GET /api/products/3 (500)', + error: { + culprit: 'logrusMiddleware', + log: { + stacktrace: [ + { + library_frame: true, + exclude_from_grouping: false, + filename: 'hook.go', + abs_path: + '/go/pkg/mod/go.elastic.co/apm/module/apmlogrus@v1.14.0/hook.go', + line: { + number: 102, + }, + module: 'go.elastic.co/apm/module/apmlogrus', + function: '(*Hook).Fire', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'hooks.go', + abs_path: + '/go/pkg/mod/github.com/sirupsen/logrus@v1.8.1/hooks.go', + line: { + number: 28, + }, + function: 'LevelHooks.Fire', + module: 'github.com/sirupsen/logrus', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'entry.go', + abs_path: + '/go/pkg/mod/github.com/sirupsen/logrus@v1.8.1/entry.go', + line: { + number: 272, + }, + module: 'github.com/sirupsen/logrus', + function: '(*Entry).fireHooks', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'entry.go', + abs_path: + '/go/pkg/mod/github.com/sirupsen/logrus@v1.8.1/entry.go', + line: { + number: 241, + }, + module: 'github.com/sirupsen/logrus', + function: '(*Entry).log', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'entry.go', + abs_path: + '/go/pkg/mod/github.com/sirupsen/logrus@v1.8.1/entry.go', + line: { + number: 293, + }, + function: '(*Entry).Log', + module: 'github.com/sirupsen/logrus', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'entry.go', + abs_path: + '/go/pkg/mod/github.com/sirupsen/logrus@v1.8.1/entry.go', + line: { + number: 338, + }, + module: 'github.com/sirupsen/logrus', + function: '(*Entry).Logf', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'entry.go', + abs_path: + '/go/pkg/mod/github.com/sirupsen/logrus@v1.8.1/entry.go', + line: { + number: 367, + }, + module: 'github.com/sirupsen/logrus', + function: '(*Entry).Errorf', + }, + { + exclude_from_grouping: false, + filename: 'logger.go', + abs_path: '/src/opbeans-go/logger.go', + line: { + number: 56, + }, + module: 'main', + function: 'logrusMiddleware', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'context.go', + abs_path: + '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go', + line: { + number: 165, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Context).Next', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'middleware.go', + abs_path: + '/go/pkg/mod/go.elastic.co/apm/module/apmgin@v1.14.0/middleware.go', + line: { + number: 98, + }, + module: 'go.elastic.co/apm/module/apmgin', + function: '(*middleware).handle', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'context.go', + abs_path: + '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go', + line: { + number: 165, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Context).Next', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'cache.go', + abs_path: + '/go/pkg/mod/github.com/gin-contrib/cache@v1.1.0/cache.go', + line: { + number: 128, + }, + module: 'github.com/gin-contrib/cache', + function: 'Cache.func1', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'context.go', + abs_path: + '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go', + line: { + number: 165, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Context).Next', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go', + filename: 'gin.go', + line: { + number: 489, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Engine).handleHTTPRequest', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'gin.go', + abs_path: '/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go', + line: { + number: 445, + }, + module: 'github.com/gin-gonic/gin', + function: '(*Engine).ServeHTTP', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'server.go', + abs_path: '/usr/local/go/src/net/http/server.go', + line: { + number: 2878, + }, + module: 'net/http', + function: 'serverHandler.ServeHTTP', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'server.go', + abs_path: '/usr/local/go/src/net/http/server.go', + line: { + number: 1929, + }, + module: 'net/http', + function: '(*conn).serve', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'asm_amd64.s', + abs_path: '/usr/local/go/src/runtime/asm_amd64.s', + line: { + number: 1581, + }, + function: 'goexit', + module: 'runtime', + }, + ], + level: 'error', + message: 'GET /api/products/3 (500)', + }, + id: '7a265a869ad88851591e0e9734aa4a70', + grouping_key: '7a640436a9be648fd708703d1ac84650', + grouping_name: 'GET /api/products/3 (500)', + }, + processor: { + name: 'error', + event: 'error', + }, + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'gke-dev-oblt-dev-oblt-pool-18e89389-qntq', + id: '5278603844673466232', + }, + provider: 'gcp', + machine: { + type: 'n1-standard-4', + }, + project: { + name: 'elastic-observability', + id: '8560181848', + }, + region: 'us-central1', + }, + observer: { + hostname: 'apm-apm-server-65d9d8dd68-zvs6p', + id: '69a7066f-46d2-42c4-a4cc-8400f60bf2b5', + ephemeral_id: '0ab88569-c301-40e9-8e78-cac7c1dac2bc', + type: 'apm-server', + version: '7.16.0', + version_major: 7, + }, + trace: { + id: 'd5e80ae688f1fef91533f02dd2bdc769', + }, + '@timestamp': '2021-10-19T13:57:02.539Z', + ecs: { + version: '1.11.0', + }, + service: { + node: { + name: '015d1127421e2c3d42a0fb031fc75e989813f58973143b6c7e33dca6ccc6f31b', + }, + environment: 'testing', + name: 'opbeans-go', + runtime: { + name: 'gc', + version: 'go1.17.2', + }, + language: { + name: 'go', + version: 'go1.17.2', + }, + version: '2021-10-14 17:49:50', + }, + host: { + os: { + platform: 'linux', + }, + ip: '10.12.0.13', + architecture: 'amd64', + }, + event: { + ingested: '2021-10-19T13:57:08.266888578Z', + }, + transaction: { + id: 'b7801be83bcdc972', + }, + timestamp: { + us: 1634651822536408, + }, + }, + ], +} as TraceAPIResponse; + +export const traceWithErrors = { + traceDocs: [ + { + container: { + id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', + }, + process: { + pid: 6, + title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java', + ppid: 1, + }, + agent: { + name: 'java', + ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb', + version: '1.14.1-SNAPSHOT', + }, + internal: { + sampler: { + value: 46, + }, + }, + source: { + ip: '172.19.0.13', + }, + processor: { + name: 'transaction', + event: 'transaction', + }, + url: { + path: '/api/orders', + scheme: 'http', + port: 3000, + domain: '172.19.0.9', + full: 'http://172.19.0.9:3000/api/orders', + }, + observer: { + hostname: 'f37f48d8b60b', + id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e', + ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9', + type: 'apm-server', + version: '8.0.0', + version_major: 8, + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T15:04:28.785Z', + ecs: { + version: '1.4.0', + }, + service: { + node: { + name: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', + }, + environment: 'production', + name: 'opbeans-java', + runtime: { + name: 'Java', + version: '10.0.2', + }, + language: { + name: 'Java', + version: '10.0.2', + }, + version: 'None', + }, + host: { + hostname: '4cf84d094553', + os: { + platform: 'Linux', + }, + ip: '172.19.0.9', + name: '4cf84d094553', + architecture: 'amd64', + }, + http: { + request: { + headers: { + Accept: ['*/*'], + 'User-Agent': ['Python/3.7 aiohttp/3.3.2'], + Host: ['172.19.0.9:3000'], + 'Accept-Encoding': ['gzip, deflate'], + }, + method: 'get', + socket: { + encrypted: false, + remote_address: '172.19.0.13', + }, + body: { + original: '[REDACTED]', + }, + }, + response: { + headers: { + 'Transfer-Encoding': ['chunked'], + Date: ['Mon, 23 Mar 2020 15:04:28 GMT'], + 'Content-Type': ['application/json;charset=ISO-8859-1'], + }, + status_code: 200, + finished: true, + headers_sent: true, + }, + version: '1.1', + }, + client: { + ip: '172.19.0.13', + }, + transaction: { + duration: { + us: 18842, + }, + result: 'HTTP 2xx', + name: 'DispatcherServlet#doGet', + id: '49809ad3c26adf74', + span_count: { + dropped: 0, + started: 1, + }, + type: 'request', + sampled: true, + }, + user_agent: { + original: 'Python/3.7 aiohttp/3.3.2', + name: 'Other', + device: { + name: 'Other', + }, + }, + timestamp: { + us: 1584975868785000, + }, + }, + { + parent: { + id: 'fc107f7b556eb49b', + }, + agent: { + name: 'go', + version: '1.7.2', + }, + processor: { + name: 'transaction', + event: 'transaction', + }, + url: { + path: '/api/orders', + scheme: 'http', + port: 3000, + domain: 'opbeans-go', + full: 'http://opbeans-go:3000/api/orders', + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T15:04:28.787Z', + service: { + node: { + name: 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29', + }, + environment: 'production', + framework: { + name: 'gin', + version: 'v1.4.0', + }, + name: 'opbeans-go', + runtime: { + name: 'gc', + version: 'go1.14.1', + }, + language: { + name: 'go', + version: 'go1.14.1', + }, + version: 'None', + }, + transaction: { + duration: { + us: 16597, + }, + result: 'HTTP 2xx', + name: 'GET /api/orders', + id: '975c8d5bfd1dd20b', + span_count: { + dropped: 0, + started: 1, + }, + type: 'request', + sampled: true, + }, + timestamp: { + us: 1584975868787052, + }, + }, + { + parent: { + id: 'daae24d83c269918', + }, + agent: { + name: 'python', + version: '5.5.2', + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + timestamp: { + us: 1584975868788603, + }, + processor: { + name: 'transaction', + event: 'transaction', + }, + url: { + path: '/api/orders', + scheme: 'http', + port: 3000, + domain: 'opbeans-go', + full: 'http://opbeans-go:3000/api/orders', + }, + '@timestamp': '2020-03-23T15:04:28.788Z', + service: { + node: { + name: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', + }, + environment: 'production', + framework: { + name: 'django', + version: '2.1.13', + }, + name: 'opbeans-python', + runtime: { + name: 'CPython', + version: '3.6.10', + }, + language: { + name: 'python', + version: '3.6.10', + }, + version: 'None', + }, + transaction: { + result: 'HTTP 2xx', + duration: { + us: 14648, + }, + name: 'GET opbeans.views.orders', + span_count: { + dropped: 0, + started: 1, + }, + id: '6fb0ff7365b87298', + type: 'request', + sampled: true, + }, + }, + { + container: { + id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', + }, + parent: { + id: '49809ad3c26adf74', + }, + process: { + pid: 6, + title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java', + ppid: 1, + }, + agent: { + name: 'java', + ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb', + version: '1.14.1-SNAPSHOT', + }, + internal: { + sampler: { + value: 44, + }, + }, + destination: { + address: 'opbeans-go', + port: 3000, + }, + processor: { + name: 'transaction', + event: 'span', + }, + observer: { + hostname: 'f37f48d8b60b', + id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e', + type: 'apm-server', + ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9', + version: '8.0.0', + version_major: 8, + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T15:04:28.785Z', + ecs: { + version: '1.4.0', + }, + service: { + node: { + name: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', + }, + environment: 'production', + name: 'opbeans-java', + runtime: { + name: 'Java', + version: '10.0.2', + }, + language: { + name: 'Java', + version: '10.0.2', + }, + version: 'None', + }, + host: { + hostname: '4cf84d094553', + os: { + platform: 'Linux', + }, + ip: '172.19.0.9', + name: '4cf84d094553', + architecture: 'amd64', + }, + connection: { + hash: "{service.environment:'production'}/{service.name:'opbeans-java'}/{span.subtype:'http'}/{destination.address:'opbeans-go'}/{span.type:'external'}", + }, + transaction: { + id: '49809ad3c26adf74', + }, + timestamp: { + us: 1584975868785273, + }, + span: { + duration: { + us: 17530, + }, + subtype: 'http', + name: 'GET opbeans-go', + destination: { + service: { + resource: 'opbeans-go:3000', + name: 'http://opbeans-go:3000', + type: 'external', + }, + }, + http: { + response: { + status_code: 200, + }, + url: { + original: 'http://opbeans-go:3000/api/orders', + }, + }, + id: 'fc107f7b556eb49b', + type: 'external', + }, + }, + { + parent: { + id: '975c8d5bfd1dd20b', + }, + agent: { + name: 'go', + version: '1.7.2', + }, + processor: { + name: 'transaction', + event: 'span', + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T15:04:28.787Z', + service: { + node: { + name: 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29', + }, + environment: 'production', + name: 'opbeans-go', + runtime: { + name: 'gc', + version: 'go1.14.1', + }, + language: { + name: 'go', + version: 'go1.14.1', + }, + version: 'None', + }, + transaction: { + id: '975c8d5bfd1dd20b', + }, + timestamp: { + us: 1584975868787174, + }, + span: { + duration: { + us: 16250, + }, + subtype: 'http', + destination: { + service: { + resource: 'opbeans-python:3000', + name: 'http://opbeans-python:3000', + type: 'external', + }, + }, + name: 'GET opbeans-python:3000', + http: { + response: { + status_code: 200, + }, + url: { + original: 'http://opbeans-python:3000/api/orders', + }, + }, + id: 'daae24d83c269918', + type: 'external', + }, + }, + { + container: { + id: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', + }, + parent: { + id: '6fb0ff7365b87298', + }, + agent: { + name: 'python', + version: '5.5.2', + }, + processor: { + name: 'transaction', + event: 'span', + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T15:04:28.790Z', + service: { + node: { + name: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', + }, + environment: 'production', + framework: { + name: 'django', + version: '2.1.13', + }, + name: 'opbeans-python', + runtime: { + name: 'CPython', + version: '3.6.10', + }, + language: { + name: 'python', + version: '3.6.10', + }, + version: 'None', + }, + transaction: { + id: '6fb0ff7365b87298', + }, + timestamp: { + us: 1584975868790080, + }, + span: { + duration: { + us: 2519, + }, + subtype: 'postgresql', + name: 'SELECT "n"."id", "n"."address", "n"."city", "n"."company_name", "n"."country", "n"."email", "n"."full_name", "n"."postal_code" FROM "customers" AS "n" WHERE "n"."id" = @__id_0 LIMIT 1', + destination: { + service: { + resource: 'postgresql', + name: 'postgresql', + type: 'db', + }, + }, + action: 'query', + id: 'c9407abb4d08ead1', + type: 'db', + sync: true, + db: { + statement: + 'SELECT "n"."id", "n"."address", "n"."city", "n"."company_name", "n"."country", "n"."email", "n"."full_name", "n"."postal_code" FROM "customers" AS "n" WHERE "n"."id" = @__id_0 LIMIT 1', + type: 'sql', + }, + }, + }, + ], + exceedsMax: false, + errorDocs: [ + { + parent: { + id: '975c8d5bfd1dd20b', + }, + agent: { + name: 'go', + version: '1.7.2', + }, + error: { + culprit: 'logrusMiddleware', + log: { + level: 'error', + message: 'GET //api/products (502)', + }, + id: '1f3cb98206b5c54225cb7c8908a658da', + grouping_key: '4dba2ff58fe6c036a5dee2ce411e512a', + }, + processor: { + name: 'error', + event: 'error', + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T16:04:28.787Z', + service: { + node: { + name: 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29', + }, + environment: 'production', + name: 'opbeans-go', + runtime: { + name: 'gc', + version: 'go1.14.1', + }, + language: { + name: 'go', + version: 'go1.14.1', + }, + version: 'None', + }, + transaction: { + id: '975c8d5bfd1dd20b', + sampled: false, + }, + timestamp: { + us: 1584975868787052, + }, + }, + { + parent: { + id: '6fb0ff7365b87298', + }, + agent: { + name: 'python', + version: '5.5.2', + }, + error: { + culprit: 'logrusMiddleware', + log: { + level: 'error', + message: 'GET //api/products (502)', + }, + id: '1f3cb98206b5c54225cb7c8908a658d2', + grouping_key: '4dba2ff58fe6c036a5dee2ce411e512a', + }, + processor: { + name: 'error', + event: 'error', + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T16:04:28.790Z', + service: { + node: { + name: 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29', + }, + environment: 'production', + name: 'opbeans-python', + runtime: { + name: 'gc', + version: 'go1.14.1', + }, + version: 'None', + }, + transaction: { + id: '6fb0ff7365b87298', + sampled: false, + }, + timestamp: { + us: 1584975868790000, + }, + }, + ], +} as unknown as TraceAPIResponse; + +export const traceChildStartBeforeParent = { + traceDocs: [ + { + container: { + id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', + }, + process: { + pid: 6, + title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java', + ppid: 1, + }, + agent: { + name: 'java', + ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb', + version: '1.14.1-SNAPSHOT', + }, + internal: { + sampler: { + value: 46, + }, + }, + source: { + ip: '172.19.0.13', + }, + processor: { + name: 'transaction', + event: 'transaction', + }, + url: { + path: '/api/orders', + scheme: 'http', + port: 3000, + domain: '172.19.0.9', + full: 'http://172.19.0.9:3000/api/orders', + }, + observer: { + hostname: 'f37f48d8b60b', + id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e', + ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9', + type: 'apm-server', + version: '8.0.0', + version_major: 8, + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T15:04:28.785Z', + ecs: { + version: '1.4.0', + }, + service: { + node: { + name: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', + }, + environment: 'production', + name: 'opbeans-java', + runtime: { + name: 'Java', + version: '10.0.2', + }, + language: { + name: 'Java', + version: '10.0.2', + }, + version: 'None', + }, + host: { + hostname: '4cf84d094553', + os: { + platform: 'Linux', + }, + ip: '172.19.0.9', + name: '4cf84d094553', + architecture: 'amd64', + }, + http: { + request: { + headers: { + Accept: ['*/*'], + 'User-Agent': ['Python/3.7 aiohttp/3.3.2'], + Host: ['172.19.0.9:3000'], + 'Accept-Encoding': ['gzip, deflate'], + }, + method: 'get', + socket: { + encrypted: false, + remote_address: '172.19.0.13', + }, + body: { + original: '[REDACTED]', + }, + }, + response: { + headers: { + 'Transfer-Encoding': ['chunked'], + Date: ['Mon, 23 Mar 2020 15:04:28 GMT'], + 'Content-Type': ['application/json;charset=ISO-8859-1'], + }, + status_code: 200, + finished: true, + headers_sent: true, + }, + version: '1.1', + }, + client: { + ip: '172.19.0.13', + }, + transaction: { + duration: { + us: 18842, + }, + result: 'HTTP 2xx', + name: 'DispatcherServlet#doGet', + id: '49809ad3c26adf74', + span_count: { + dropped: 0, + started: 1, + }, + type: 'request', + sampled: true, + }, + user_agent: { + original: 'Python/3.7 aiohttp/3.3.2', + name: 'Other', + device: { + name: 'Other', + }, + }, + timestamp: { + us: 1584975868785000, + }, + }, + { + parent: { + id: 'fc107f7b556eb49b', + }, + agent: { + name: 'go', + version: '1.7.2', + }, + processor: { + name: 'transaction', + event: 'transaction', + }, + url: { + path: '/api/orders', + scheme: 'http', + port: 3000, + domain: 'opbeans-go', + full: 'http://opbeans-go:3000/api/orders', + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T15:04:28.787Z', + service: { + node: { + name: 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29', + }, + environment: 'production', + framework: { + name: 'gin', + version: 'v1.4.0', + }, + name: 'opbeans-go', + runtime: { + name: 'gc', + version: 'go1.14.1', + }, + language: { + name: 'go', + version: 'go1.14.1', + }, + version: 'None', + }, + transaction: { + duration: { + us: 16597, + }, + result: 'HTTP 2xx', + name: 'GET /api/orders', + id: '975c8d5bfd1dd20b', + span_count: { + dropped: 0, + started: 1, + }, + type: 'request', + sampled: true, + }, + timestamp: { + us: 1584975868787052, + }, + }, + { + parent: { + id: 'daae24d83c269918', + }, + agent: { + name: 'python', + version: '5.5.2', + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + timestamp: { + us: 1584975868780000, + }, + processor: { + name: 'transaction', + event: 'transaction', + }, + url: { + path: '/api/orders', + scheme: 'http', + port: 3000, + domain: 'opbeans-go', + full: 'http://opbeans-go:3000/api/orders', + }, + '@timestamp': '2020-03-23T15:04:28.788Z', + service: { + node: { + name: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', + }, + environment: 'production', + framework: { + name: 'django', + version: '2.1.13', + }, + name: 'opbeans-python', + runtime: { + name: 'CPython', + version: '3.6.10', + }, + language: { + name: 'python', + version: '3.6.10', + }, + version: 'None', + }, + transaction: { + result: 'HTTP 2xx', + duration: { + us: 1464, + }, + name: 'I started before my parent 😰', + span_count: { + dropped: 0, + started: 1, + }, + id: '6fb0ff7365b87298', + type: 'request', + sampled: true, + }, + }, + { + container: { + id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', + }, + parent: { + id: '49809ad3c26adf74', + }, + process: { + pid: 6, + title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java', + ppid: 1, + }, + agent: { + name: 'java', + ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb', + version: '1.14.1-SNAPSHOT', + }, + internal: { + sampler: { + value: 44, + }, + }, + destination: { + address: 'opbeans-go', + port: 3000, + }, + processor: { + name: 'transaction', + event: 'span', + }, + observer: { + hostname: 'f37f48d8b60b', + id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e', + type: 'apm-server', + ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9', + version: '8.0.0', + version_major: 8, + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T15:04:28.785Z', + ecs: { + version: '1.4.0', + }, + service: { + node: { + name: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e', + }, + environment: 'production', + name: 'opbeans-java', + runtime: { + name: 'Java', + version: '10.0.2', + }, + language: { + name: 'Java', + version: '10.0.2', + }, + version: 'None', + }, + host: { + hostname: '4cf84d094553', + os: { + platform: 'Linux', + }, + ip: '172.19.0.9', + name: '4cf84d094553', + architecture: 'amd64', + }, + connection: { + hash: "{service.environment:'production'}/{service.name:'opbeans-java'}/{span.subtype:'http'}/{destination.address:'opbeans-go'}/{span.type:'external'}", + }, + transaction: { + id: '49809ad3c26adf74', + }, + timestamp: { + us: 1584975868785273, + }, + span: { + duration: { + us: 17530, + }, + subtype: 'http', + name: 'GET opbeans-go', + destination: { + service: { + resource: 'opbeans-go:3000', + name: 'http://opbeans-go:3000', + type: 'external', + }, + }, + http: { + response: { + status_code: 200, + }, + url: { + original: 'http://opbeans-go:3000/api/orders', + }, + }, + id: 'fc107f7b556eb49b', + type: 'external', + }, + }, + { + parent: { + id: '975c8d5bfd1dd20b', + }, + agent: { + name: 'go', + version: '1.7.2', + }, + processor: { + name: 'transaction', + event: 'span', + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T15:04:28.787Z', + service: { + node: { + name: 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29', + }, + environment: 'production', + name: 'opbeans-go', + runtime: { + name: 'gc', + version: 'go1.14.1', + }, + language: { + name: 'go', + version: 'go1.14.1', + }, + version: 'None', + }, + transaction: { + id: '975c8d5bfd1dd20b', + }, + timestamp: { + us: 1584975868787174, + }, + span: { + duration: { + us: 16250, + }, + subtype: 'http', + destination: { + service: { + resource: 'opbeans-python:3000', + name: 'http://opbeans-python:3000', + type: 'external', + }, + }, + name: 'I am his 👇🏻 parent 😡', + http: { + response: { + status_code: 200, + }, + url: { + original: 'http://opbeans-python:3000/api/orders', + }, + }, + id: 'daae24d83c269918', + type: 'external', + }, + }, + { + container: { + id: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', + }, + parent: { + id: '6fb0ff7365b87298', + }, + agent: { + name: 'python', + version: '5.5.2', + }, + processor: { + name: 'transaction', + event: 'span', + }, + trace: { + id: '513d33fafe99bbe6134749310c9b5322', + }, + '@timestamp': '2020-03-23T15:04:28.790Z', + service: { + node: { + name: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51', + }, + environment: 'production', + framework: { + name: 'django', + version: '2.1.13', + }, + name: 'opbeans-python', + runtime: { + name: 'CPython', + version: '3.6.10', + }, + language: { + name: 'python', + version: '3.6.10', + }, + version: 'None', + }, + transaction: { + id: '6fb0ff7365b87298', + }, + timestamp: { + us: 1584975868781000, + }, + span: { + duration: { + us: 2519, + }, + subtype: 'postgresql', + name: 'I am using my parents skew 😇', + destination: { + service: { + resource: 'postgresql', + name: 'postgresql', + type: 'db', + }, + }, + action: 'query', + id: 'c9407abb4d08ead1', + type: 'db', + sync: true, + db: { + statement: + 'SELECT "opbeans_order"."id", "opbeans_order"."customer_id", "opbeans_customer"."full_name", "opbeans_order"."created_at" FROM "opbeans_order" INNER JOIN "opbeans_customer" ON ("opbeans_order"."customer_id" = "opbeans_customer"."id") LIMIT 1000', + type: 'sql', + }, + }, + }, + ], + exceedsMax: false, + errorDocs: [], +} as TraceAPIResponse; + +export const inferredSpans = { + traceDocs: [ + { + container: { + id: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', + }, + agent: { + name: 'java', + ephemeral_id: '1cb5c830-c677-4b13-b340-ab1502f527c3', + version: '1.15.1-SNAPSHOT', + }, + process: { + pid: 6, + title: '/opt/java/openjdk/bin/java', + ppid: 1, + }, + source: { + ip: '172.18.0.8', + }, + processor: { + name: 'transaction', + event: 'transaction', + }, + url: { + path: '/api/products/2', + scheme: 'http', + port: 3000, + domain: '172.18.0.7', + full: 'http://172.18.0.7:3000/api/products/2', + }, + observer: { + hostname: '7189f754b5a3', + id: 'f32d8d9f-a9f9-4355-8370-548dfd8024dc', + ephemeral_id: 'bff20764-0195-4f78-aa84-d799fc47b954', + type: 'apm-server', + version: '8.0.0', + version_major: 8, + }, + trace: { + id: '3b0dc77f3754e5bcb9da0e4c15e0db97', + }, + '@timestamp': '2020-04-09T11:36:00.786Z', + ecs: { + version: '1.5.0', + }, + service: { + node: { + name: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', + }, + environment: 'production', + name: 'opbeans-java', + runtime: { + name: 'Java', + version: '11.0.6', + }, + language: { + name: 'Java', + version: '11.0.6', + }, + version: 'None', + }, + host: { + hostname: 'fc2ae281f56f', + os: { + platform: 'Linux', + }, + ip: '172.18.0.7', + name: 'fc2ae281f56f', + architecture: 'amd64', + }, + client: { + ip: '172.18.0.8', + }, + http: { + request: { + headers: { + Accept: ['*/*'], + 'User-Agent': ['Python/3.7 aiohttp/3.3.2'], + Host: ['172.18.0.7:3000'], + 'Accept-Encoding': ['gzip, deflate'], + }, + method: 'get', + socket: { + encrypted: false, + remote_address: '172.18.0.8', + }, + }, + response: { + headers: { + 'Transfer-Encoding': ['chunked'], + Date: ['Thu, 09 Apr 2020 11:36:01 GMT'], + 'Content-Type': ['application/json;charset=UTF-8'], + }, + status_code: 200, + finished: true, + headers_sent: true, + }, + version: '1.1', + }, + user_agent: { + original: 'Python/3.7 aiohttp/3.3.2', + name: 'Other', + device: { + name: 'Other', + }, + }, + transaction: { + duration: { + us: 237537, + }, + result: 'HTTP 2xx', + name: 'APIRestController#product', + span_count: { + dropped: 0, + started: 3, + }, + id: 'f2387d37260d00bd', + type: 'request', + sampled: true, + }, + timestamp: { + us: 1586432160786001, + }, + }, + { + container: { + id: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', + }, + parent: { + id: 'f2387d37260d00bd', + }, + agent: { + name: 'java', + ephemeral_id: '1cb5c830-c677-4b13-b340-ab1502f527c3', + version: '1.15.1-SNAPSHOT', + }, + process: { + pid: 6, + title: '/opt/java/openjdk/bin/java', + ppid: 1, + }, + processor: { + name: 'transaction', + event: 'span', + }, + observer: { + hostname: '7189f754b5a3', + id: 'f32d8d9f-a9f9-4355-8370-548dfd8024dc', + ephemeral_id: 'bff20764-0195-4f78-aa84-d799fc47b954', + type: 'apm-server', + version: '8.0.0', + version_major: 8, + }, + trace: { + id: '3b0dc77f3754e5bcb9da0e4c15e0db97', + }, + '@timestamp': '2020-04-09T11:36:00.810Z', + ecs: { + version: '1.5.0', + }, + service: { + node: { + name: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', + }, + environment: 'production', + name: 'opbeans-java', + runtime: { + name: 'Java', + version: '11.0.6', + }, + language: { + name: 'Java', + version: '11.0.6', + }, + version: 'None', + }, + host: { + hostname: 'fc2ae281f56f', + os: { + platform: 'Linux', + }, + ip: '172.18.0.7', + name: 'fc2ae281f56f', + architecture: 'amd64', + }, + transaction: { + id: 'f2387d37260d00bd', + }, + span: { + duration: { + us: 204574, + }, + subtype: 'inferred', + name: 'ServletInvocableHandlerMethod#invokeAndHandle', + id: 'a5df600bd7bd5e38', + type: 'app', + }, + timestamp: { + us: 1586432160810441, + }, + }, + { + container: { + id: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', + }, + parent: { + id: 'a5df600bd7bd5e38', + }, + agent: { + name: 'java', + ephemeral_id: '1cb5c830-c677-4b13-b340-ab1502f527c3', + version: '1.15.1-SNAPSHOT', + }, + process: { + pid: 6, + title: '/opt/java/openjdk/bin/java', + ppid: 1, + }, + processor: { + name: 'transaction', + event: 'span', + }, + observer: { + hostname: '7189f754b5a3', + id: 'f32d8d9f-a9f9-4355-8370-548dfd8024dc', + type: 'apm-server', + ephemeral_id: 'bff20764-0195-4f78-aa84-d799fc47b954', + version: '8.0.0', + version_major: 8, + }, + trace: { + id: '3b0dc77f3754e5bcb9da0e4c15e0db97', + }, + '@timestamp': '2020-04-09T11:36:00.810Z', + ecs: { + version: '1.5.0', + }, + service: { + node: { + name: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', + }, + environment: 'production', + name: 'opbeans-java', + runtime: { + name: 'Java', + version: '11.0.6', + }, + language: { + name: 'Java', + version: '11.0.6', + }, + version: 'None', + }, + host: { + hostname: 'fc2ae281f56f', + os: { + platform: 'Linux', + }, + ip: '172.18.0.7', + name: 'fc2ae281f56f', + architecture: 'amd64', + }, + transaction: { + id: 'f2387d37260d00bd', + }, + timestamp: { + us: 1586432160810441, + }, + span: { + duration: { + us: 102993, + }, + stacktrace: [ + { + library_frame: true, + exclude_from_grouping: false, + filename: 'InvocableHandlerMethod.java', + line: { + number: -1, + }, + function: 'doInvoke', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'InvocableHandlerMethod.java', + line: { + number: -1, + }, + function: 'invokeForRequest', + }, + ], + subtype: 'inferred', + name: 'APIRestController#product', + id: '808dc34fc41ce522', + type: 'app', + }, + }, + { + container: { + id: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', + }, + parent: { + id: 'f2387d37260d00bd', + }, + agent: { + name: 'java', + ephemeral_id: '1cb5c830-c677-4b13-b340-ab1502f527c3', + version: '1.15.1-SNAPSHOT', + }, + process: { + pid: 6, + title: '/opt/java/openjdk/bin/java', + ppid: 1, + }, + processor: { + name: 'transaction', + event: 'span', + }, + labels: { + productId: '2', + }, + observer: { + hostname: '7189f754b5a3', + id: 'f32d8d9f-a9f9-4355-8370-548dfd8024dc', + ephemeral_id: 'bff20764-0195-4f78-aa84-d799fc47b954', + type: 'apm-server', + version: '8.0.0', + version_major: 8, + }, + trace: { + id: '3b0dc77f3754e5bcb9da0e4c15e0db97', + }, + '@timestamp': '2020-04-09T11:36:00.832Z', + ecs: { + version: '1.5.0', + }, + service: { + node: { + name: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', + }, + environment: 'production', + name: 'opbeans-java', + runtime: { + name: 'Java', + version: '11.0.6', + }, + language: { + name: 'Java', + version: '11.0.6', + }, + version: 'None', + }, + host: { + hostname: 'fc2ae281f56f', + os: { + platform: 'Linux', + }, + ip: '172.18.0.7', + name: 'fc2ae281f56f', + architecture: 'amd64', + }, + transaction: { + id: 'f2387d37260d00bd', + }, + timestamp: { + us: 1586432160832300, + }, + span: { + duration: { + us: 99295, + }, + name: 'OpenTracing product span', + id: '41226ae63af4f235', + type: 'unknown', + }, + child: { id: ['8d80de06aa11a6fc'] }, + }, + { + container: { + id: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', + }, + parent: { + id: '808dc34fc41ce522', + }, + process: { + pid: 6, + title: '/opt/java/openjdk/bin/java', + ppid: 1, + }, + agent: { + name: 'java', + ephemeral_id: '1cb5c830-c677-4b13-b340-ab1502f527c3', + version: '1.15.1-SNAPSHOT', + }, + processor: { + name: 'transaction', + event: 'span', + }, + observer: { + hostname: '7189f754b5a3', + id: 'f32d8d9f-a9f9-4355-8370-548dfd8024dc', + ephemeral_id: 'bff20764-0195-4f78-aa84-d799fc47b954', + type: 'apm-server', + version: '8.0.0', + version_major: 8, + }, + trace: { + id: '3b0dc77f3754e5bcb9da0e4c15e0db97', + }, + '@timestamp': '2020-04-09T11:36:00.859Z', + ecs: { + version: '1.5.0', + }, + service: { + node: { + name: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', + }, + environment: 'production', + name: 'opbeans-java', + runtime: { + name: 'Java', + version: '11.0.6', + }, + language: { + name: 'Java', + version: '11.0.6', + }, + version: 'None', + }, + host: { + hostname: 'fc2ae281f56f', + os: { + platform: 'Linux', + }, + ip: '172.18.0.7', + name: 'fc2ae281f56f', + architecture: 'amd64', + }, + transaction: { + id: 'f2387d37260d00bd', + }, + timestamp: { + us: 1586432160859600, + }, + span: { + duration: { + us: 53835, + }, + subtype: 'inferred', + name: 'Loader#executeQueryStatement', + id: '8d80de06aa11a6fc', + type: 'app', + }, + }, + { + container: { + id: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', + }, + parent: { + id: '41226ae63af4f235', + }, + agent: { + name: 'java', + ephemeral_id: '1cb5c830-c677-4b13-b340-ab1502f527c3', + version: '1.15.1-SNAPSHOT', + }, + process: { + pid: 6, + title: '/opt/java/openjdk/bin/java', + ppid: 1, + }, + destination: { + address: 'postgres', + port: 5432, + }, + processor: { + name: 'transaction', + event: 'span', + }, + observer: { + hostname: '7189f754b5a3', + id: 'f32d8d9f-a9f9-4355-8370-548dfd8024dc', + ephemeral_id: 'bff20764-0195-4f78-aa84-d799fc47b954', + type: 'apm-server', + version: '8.0.0', + version_major: 8, + }, + trace: { + id: '3b0dc77f3754e5bcb9da0e4c15e0db97', + }, + '@timestamp': '2020-04-09T11:36:00.903Z', + ecs: { + version: '1.5.0', + }, + service: { + node: { + name: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', + }, + environment: 'production', + name: 'opbeans-java', + runtime: { + name: 'Java', + version: '11.0.6', + }, + language: { + name: 'Java', + version: '11.0.6', + }, + version: 'None', + }, + host: { + hostname: 'fc2ae281f56f', + os: { + platform: 'Linux', + }, + ip: '172.18.0.7', + name: 'fc2ae281f56f', + architecture: 'amd64', + }, + transaction: { + id: 'f2387d37260d00bd', + }, + timestamp: { + us: 1586432160903236, + }, + span: { + duration: { + us: 10211, + }, + subtype: 'postgresql', + destination: { + service: { + resource: 'postgresql', + name: 'postgresql', + type: 'db', + }, + }, + name: 'SELECT FROM products', + action: 'query', + id: '3708d5623658182f', + type: 'db', + db: { + statement: + 'select product0_.id as col_0_0_, product0_.sku as col_1_0_, product0_.name as col_2_0_, product0_.description as col_3_0_, product0_.cost as col_4_0_, product0_.selling_price as col_5_0_, product0_.stock as col_6_0_, producttyp1_.id as col_7_0_, producttyp1_.name as col_8_0_, (select sum(orderline2_.amount) from order_lines orderline2_ where orderline2_.product_id=product0_.id) as col_9_0_ from products product0_ left outer join product_types producttyp1_ on product0_.type_id=producttyp1_.id where product0_.id=?', + type: 'sql', + user: { + name: 'postgres', + }, + }, + }, + }, + { + container: { + id: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', + }, + parent: { + id: '41226ae63af4f235', + }, + process: { + pid: 6, + title: '/opt/java/openjdk/bin/java', + ppid: 1, + }, + agent: { + name: 'java', + ephemeral_id: '1cb5c830-c677-4b13-b340-ab1502f527c3', + version: '1.15.1-SNAPSHOT', + }, + destination: { + address: 'postgres', + port: 5432, + }, + processor: { + name: 'transaction', + event: 'span', + }, + observer: { + hostname: '7189f754b5a3', + id: 'f32d8d9f-a9f9-4355-8370-548dfd8024dc', + ephemeral_id: 'bff20764-0195-4f78-aa84-d799fc47b954', + type: 'apm-server', + version: '8.0.0', + version_major: 8, + }, + trace: { + id: '3b0dc77f3754e5bcb9da0e4c15e0db97', + }, + '@timestamp': '2020-04-09T11:36:00.859Z', + ecs: { + version: '1.5.0', + }, + service: { + node: { + name: 'fc2ae281f56fb84728bc9b5e6c17f3d13bbb7f4efd461158558e5c38e655abad', + }, + environment: 'production', + name: 'opbeans-java', + runtime: { + name: 'Java', + version: '11.0.6', + }, + language: { + name: 'Java', + version: '11.0.6', + }, + version: 'None', + }, + host: { + hostname: 'fc2ae281f56f', + os: { + platform: 'Linux', + }, + ip: '172.18.0.7', + name: 'fc2ae281f56f', + architecture: 'amd64', + }, + transaction: { + id: 'f2387d37260d00bd', + }, + timestamp: { + us: 1586432160859508, + }, + span: { + duration: { + us: 4503, + }, + subtype: 'postgresql', + destination: { + service: { + resource: 'postgresql', + name: 'postgresql', + type: 'db', + }, + }, + name: 'empty query', + action: 'query', + id: '9871cfd612368932', + type: 'db', + db: { + rows_affected: 0, + statement: '(empty query)', + type: 'sql', + user: { + name: 'postgres', + }, + }, + }, + }, + ], + exceedsMax: false, + errorDocs: [], +} as TraceAPIResponse; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.stories.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.stories.tsx new file mode 100644 index 0000000000000..895b83136a097 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.stories.tsx @@ -0,0 +1,88 @@ +/* + * Copyright 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, { ComponentProps } from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; +import { WaterfallContainer } from './index'; +import { getWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers'; +import { + inferredSpans, + manyChildrenWithSameLength, + simpleTrace, + traceChildStartBeforeParent, + traceWithErrors, + urlParams as testUrlParams, +} from './waterfall_container.stories.data'; + +type Args = ComponentProps; + +const stories: Meta = { + title: 'app/TransactionDetails/Waterfall', + component: WaterfallContainer, + decorators: [ + (StoryComponent) => ( + + + + + + ), + ], +}; +export default stories; + +export const Example: Story = ({ urlParams, waterfall }) => { + return ; +}; +Example.args = { + urlParams: testUrlParams, + waterfall: getWaterfall(simpleTrace, '975c8d5bfd1dd20b'), +}; + +export const WithErrors: Story = ({ urlParams, waterfall }) => { + return ; +}; +WithErrors.args = { + urlParams: testUrlParams, + waterfall: getWaterfall(traceWithErrors, '975c8d5bfd1dd20b'), +}; + +export const ChildStartsBeforeParent: Story = ({ + urlParams, + waterfall, +}) => { + return ; +}; +ChildStartsBeforeParent.args = { + urlParams: testUrlParams, + waterfall: getWaterfall(traceChildStartBeforeParent, '975c8d5bfd1dd20b'), +}; + +export const InferredSpans: Story = ({ urlParams, waterfall }) => { + return ; +}; +InferredSpans.args = { + urlParams: testUrlParams, + waterfall: getWaterfall(inferredSpans, 'f2387d37260d00bd'), +}; + +export const ManyChildrenWithSameLength: Story = ({ + urlParams, + waterfall, +}) => { + return ; +}; +ManyChildrenWithSameLength.args = { + urlParams: testUrlParams, + waterfall: getWaterfall(manyChildrenWithSameLength, '9a7f717439921d39'), +}; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.test.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.test.tsx new file mode 100644 index 0000000000000..47610569dfa06 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.test.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { composeStories } from '@storybook/testing-react'; +import { render } from '@testing-library/react'; +import React from 'react'; +import { disableConsoleWarning } from '../../../../../utils/testHelpers'; +import * as stories from './waterfall_container.stories'; + +const { Example } = composeStories(stories); + +describe('WaterfallContainer', () => { + let consoleMock: jest.SpyInstance; + + beforeAll(() => { + consoleMock = disableConsoleWarning('Warning: componentWillReceiveProps'); + }); + + afterAll(() => { + consoleMock.mockRestore(); + }); + + it('renders', () => { + expect(() => render()).not.toThrowError(); + }); + + it('expands and contracts the accordion', () => { + const { getAllByRole } = render(); + const buttons = getAllByRole('button'); + const parentItem = buttons[2]; + const childItem = buttons[3]; + + parentItem.click(); + + expect(parentItem).toHaveAttribute('aria-expanded', 'false'); + expect(childItem).toHaveAttribute('aria-expanded', 'true'); + }); +}); diff --git a/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx b/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx index 5377cb81b372e..7e6ee849fdb0b 100644 --- a/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx +++ b/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx @@ -53,12 +53,6 @@ const apmRoutes = route([ }), }), ]), - defaults: { - query: { - rangeFrom: 'now-15m', - rangeTo: 'now', - }, - }, }, { path: '/', diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.test.tsx index 48bd992952c30..8a57063ac4d45 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.test.tsx @@ -29,8 +29,8 @@ describe('TransactionDistributionChart', () => { { doc_count: 10 }, { doc_count: 10 }, { doc_count: 0.0001 }, - { doc_count: 0 }, - { doc_count: 0 }, + { doc_count: 0.0001 }, + { doc_count: 0.0001 }, { doc_count: 10 }, { doc_count: 10 }, { doc_count: 0.0001 }, 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 535fb777166bb..1a1ea13fa45ec 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 @@ -51,6 +51,7 @@ interface TransactionDistributionChartProps { markerValue: number; markerPercentile: number; onChartSelection?: BrushEndListener; + palette?: string[]; selection?: [number, number]; status: FETCH_STATUS; } @@ -77,13 +78,10 @@ const CHART_PLACEHOLDER_VALUE = 0.0001; // Elastic charts will show any lone bin (i.e. a populated bin followed by empty bin) // as a circular marker instead of a bar // This provides a workaround by making the next bin not empty +// TODO Find a way to get rid of this workaround since it alters original values of the data. export const replaceHistogramDotsWithBars = (histogramItems: HistogramItem[]) => histogramItems.reduce((histogramItem, _, i) => { - if ( - histogramItem[i - 1]?.doc_count > 0 && - histogramItem[i - 1]?.doc_count !== CHART_PLACEHOLDER_VALUE && - histogramItem[i].doc_count === 0 - ) { + if (histogramItem[i].doc_count === 0) { histogramItem[i].doc_count = CHART_PLACEHOLDER_VALUE; } return histogramItem; @@ -102,16 +100,16 @@ export function TransactionDistributionChart({ markerValue, markerPercentile, onChartSelection, + palette, selection, status, }: TransactionDistributionChartProps) { const chartTheme = useChartTheme(); const euiTheme = useTheme(); - const areaSeriesColors = [ + const areaSeriesColors = palette ?? [ euiTheme.eui.euiColorVis1, euiTheme.eui.euiColorVis2, - euiTheme.eui.euiColorVis5, ]; const annotationsDataValues: LineAnnotationDatum[] = [ @@ -136,7 +134,7 @@ export function TransactionDistributionChart({ ) ?? 0; const yTicks = Math.ceil(Math.log10(yMax)); const yAxisDomain = { - min: 0.9, + min: 0.5, max: Math.pow(10, yTicks), }; @@ -171,7 +169,7 @@ export function TransactionDistributionChart({ }, areaSeriesStyle: { line: { - visible: false, + visible: true, }, }, axes: { @@ -186,7 +184,7 @@ export function TransactionDistributionChart({ }, }, }} - showLegend + showLegend={true} legendPosition={Position.Bottom} onBrushEnd={onChartSelection} /> @@ -238,7 +236,10 @@ export function TransactionDistributionChart({ /> = ({ variant, results }) => { > {results.length > 0 ? ( results.map((result, index) => ( - + { + beforeEach(() => { + jest.clearAllMocks(); + setMockValues({ engineName: 'some-engine' }); + }); + + it('renders', () => { + const wrapper = shallow(); + + 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' + ); + }); +}); 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 new file mode 100644 index 0000000000000..c3c5747b29954 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/automated_curations_history_panel.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useValues } from 'kea'; + +import { i18n } from '@kbn/i18n'; + +import { EntSearchLogStream } from '../../../../../../shared/log_stream'; +import { DataPanel } from '../../../../data_panel'; +import { EngineLogic } from '../../../../engine'; + +export const AutomatedCurationsHistoryPanel: React.FC = () => { + const { engineName } = useValues(EngineLogic); + + const filters = [ + 'event.kind: event', + 'event.dataset: search-relevance-suggestions', + `appsearch.search_relevance_suggestions.engine: ${engineName}`, + 'event.action: curation_suggestion', + 'appsearch.search_relevance_suggestions.suggestion.new_status: automated', + ]; + + return ( + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.automatedCurationsHistoryPanel.tableTitle', + { + defaultMessage: 'Automated curation changes', + } + )} + + } + subtitle={i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.automatedCurationsHistoryPanel.tableDecription', + { + defaultMessage: 'A detailed log of recent changes to your automated curations.', + } + )} + hasBorder + > + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/curation_changes_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/curation_changes_panel.test.tsx deleted file mode 100644 index 7fc06beaa86a9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/curation_changes_panel.test.tsx +++ /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 React from 'react'; - -import { shallow } from 'enzyme'; - -import { DataPanel } from '../../../../data_panel'; - -import { CurationChangesPanel } from './curation_changes_panel'; - -describe('CurationChangesPanel', () => { - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.is(DataPanel)).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/curation_changes_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/curation_changes_panel.tsx deleted file mode 100644 index 0aaf20485966e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/curation_changes_panel.tsx +++ /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 React from 'react'; - -import { DataPanel } from '../../../../data_panel'; - -export const CurationChangesPanel: React.FC = () => { - return ( - Automated curation changes} - subtitle={A detailed log of recent changes to your automated curations} - iconType="visTable" - hasBorder - > - Embedded logs view goes here... - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/index.ts index 43651e613364e..2f2c2aeb63503 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/index.ts @@ -5,6 +5,6 @@ * 2.0. */ -export { CurationChangesPanel } from './curation_changes_panel'; +export { AutomatedCurationsHistoryPanel } from './automated_curations_history_panel'; export { IgnoredQueriesPanel } from './ignored_queries_panel'; -export { RejectedCurationsPanel } from './rejected_curations_panel'; +export { RejectedCurationsHistoryPanel } from './rejected_curations_history_panel'; 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 new file mode 100644 index 0000000000000..28bb317941e1c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_history_panel.test.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// TODO use engine_logic.mock instead of setMockValues({ engineName: 'some-engine' }), currently that breaks the test +import { setMockValues } from '../../../../../../__mocks__/kea_logic'; +// import '../../../../../__mocks__/engine_logic.mock'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EntSearchLogStream } from '../../../../../../shared/log_stream'; +import { DataPanel } from '../../../../data_panel'; + +import { RejectedCurationsHistoryPanel } from './rejected_curations_history_panel'; + +describe('RejectedCurationsHistoryPanel', () => { + beforeEach(() => { + jest.clearAllMocks(); + setMockValues({ engineName: 'some-engine' }); + }); + + it('renders', () => { + const wrapper = shallow(); + + 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' + ); + }); +}); 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 new file mode 100644 index 0000000000000..275083f91c0fb --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_history_panel.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useValues } from 'kea'; + +import { i18n } from '@kbn/i18n'; + +import { EntSearchLogStream } from '../../../../../../shared/log_stream'; +import { DataPanel } from '../../../../data_panel'; +import { EngineLogic } from '../../../../engine'; + +export const RejectedCurationsHistoryPanel: React.FC = () => { + const { engineName } = useValues(EngineLogic); + + const filters = [ + 'event.kind: event', + 'event.dataset: search-relevance-suggestions', + `appsearch.search_relevance_suggestions.engine: ${engineName}`, + 'event.action: curation_suggestion', + 'appsearch.search_relevance_suggestions.suggestion.new_status: rejected', + ]; + + return ( + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.rejectedCurationsHistoryPanel.tableTitle', + { + defaultMessage: 'Recently rejected suggestions', + } + )} + + } + subtitle={i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.rejectedCurationsHistoryPanel.tableDescription', + { + defaultMessage: 'View suggestions you’ve previously rejected.', + } + )} + hasBorder + > + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_panel.test.tsx deleted file mode 100644 index a40eb8895ad69..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_panel.test.tsx +++ /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 React from 'react'; - -import { shallow } from 'enzyme'; - -import { DataPanel } from '../../../../data_panel'; - -import { RejectedCurationsPanel } from './rejected_curations_panel'; - -describe('RejectedCurationsPanel', () => { - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.is(DataPanel)).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_panel.tsx deleted file mode 100644 index 51719b4eebbd7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_panel.tsx +++ /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 React from 'react'; - -import { DataPanel } from '../../../../data_panel'; - -export const RejectedCurationsPanel: React.FC = () => { - return ( - Rececntly rejected sugggestions} - subtitle={Recent suggestions that are still valid can be re-enabled from here} - iconType="crossInACircleFilled" - hasBorder - > - Embedded logs view goes here... - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/curations_history.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/curations_history.test.tsx index 407454922ef05..c5dc20024b62f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/curations_history.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/curations_history.test.tsx @@ -9,15 +9,19 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { CurationChangesPanel, IgnoredQueriesPanel, RejectedCurationsPanel } from './components'; +import { + AutomatedCurationsHistoryPanel, + IgnoredQueriesPanel, + RejectedCurationsHistoryPanel, +} from './components'; import { CurationsHistory } from './curations_history'; describe('CurationsHistory', () => { it('renders', () => { const wrapper = shallow(); - expect(wrapper.find(CurationChangesPanel)).toHaveLength(1); - expect(wrapper.find(RejectedCurationsPanel)).toHaveLength(1); + expect(wrapper.find(AutomatedCurationsHistoryPanel)).toHaveLength(1); + expect(wrapper.find(RejectedCurationsHistoryPanel)).toHaveLength(1); expect(wrapper.find(IgnoredQueriesPanel)).toHaveLength(1); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/curations_history.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/curations_history.tsx index 5f857087e05ef..41250d8f67dfd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/curations_history.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/curations_history.tsx @@ -9,7 +9,11 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { CurationChangesPanel, IgnoredQueriesPanel, RejectedCurationsPanel } from './components'; +import { + AutomatedCurationsHistoryPanel, + IgnoredQueriesPanel, + RejectedCurationsHistoryPanel, +} from './components'; export const CurationsHistory: React.FC = () => { return ( @@ -17,10 +21,10 @@ export const CurationsHistory: React.FC = () => { - + - + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts index ab45c54cc5c57..2a982c2be1cfc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts @@ -130,7 +130,7 @@ interface SourceActivity { export interface SyncEstimate { duration?: string; - nextStart: string; + nextStart?: string; lastRun?: string; } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency_item.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency_item.test.tsx index 7498a185a80ec..12cd07ac23b7c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency_item.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency_item.test.tsx @@ -122,4 +122,10 @@ describe('FrequencyItem', () => { expect(setSyncFrequency).toHaveBeenCalledWith('full', '3', 'minutes'); }); }); + + it('handles edge case where estimate is empty', () => { + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="SyncEstimates"]')).toHaveLength(0); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency_item.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency_item.tsx index a51500e3076a8..f0066f06466a7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency_item.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency_item.tsx @@ -53,6 +53,7 @@ export const FrequencyItem: React.FC = ({ const estimateDisplay = durationEstimate && moment.duration(durationEstimate).humanize(); const nextStartIsPast = moment().isAfter(nextStart); const nextStartTime = nextStartIsPast ? NEXT_SYNC_RUNNING_MESSAGE : moment(nextStart).fromNow(); + const showEstimates = lastRun || nextStart || durationEstimate; const frequencyItemLabel = ( = ({ - - - {lastRun && lastRunSummary} {nextStartSummary} {estimateDisplay && estimateSummary} - + {showEstimates && ( + <> + + + {lastRun && lastRunSummary} {nextStart && nextStartSummary}{' '} + {estimateDisplay && estimateSummary} + + + )} ); diff --git a/x-pack/plugins/infra/public/pages/logs/settings/fields_configuration_panel.tsx b/x-pack/plugins/infra/public/pages/logs/settings/fields_configuration_panel.tsx deleted file mode 100644 index d21b0b0588dea..0000000000000 --- a/x-pack/plugins/infra/public/pages/logs/settings/fields_configuration_panel.tsx +++ /dev/null @@ -1,190 +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 { - EuiCallOut, - EuiCode, - EuiDescribedFormGroup, - EuiFieldText, - EuiFormRow, - EuiLink, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useMemo } from 'react'; -import { FormElement } from './form_elements'; -import { getFormRowProps, getStringInputFieldProps } from './form_field_props'; -import { FormValidationError } from './validation_errors'; - -interface FieldsConfigurationPanelProps { - isLoading: boolean; - isReadOnly: boolean; - tiebreakerFieldFormElement: FormElement; - timestampFieldFormElement: FormElement; -} - -export const FieldsConfigurationPanel = ({ - isLoading, - isReadOnly, - tiebreakerFieldFormElement, - timestampFieldFormElement, -}: FieldsConfigurationPanelProps) => { - const isTimestampValueDefault = timestampFieldFormElement.value === '@timestamp'; - const isTiebreakerValueDefault = tiebreakerFieldFormElement.value === '_doc'; - - return ( - <> - -

- -

-
- - -

- - - - ), - ecsLink: ( - - ECS - - ), - }} - /> -

-
- - - - - } - description={ - - } - > - @timestamp, - }} - /> - } - label={ - - } - {...useMemo( - () => getFormRowProps(timestampFieldFormElement), - [timestampFieldFormElement] - )} - > - getStringInputFieldProps(timestampFieldFormElement), - [timestampFieldFormElement] - )} - /> - - - - - - } - description={ - - } - > - _doc, - }} - /> - } - label={ - - } - {...useMemo( - () => getFormRowProps(tiebreakerFieldFormElement), - [tiebreakerFieldFormElement] - )} - > - getStringInputFieldProps(tiebreakerFieldFormElement), - [tiebreakerFieldFormElement] - )} - /> - - - - ); -}; diff --git a/x-pack/plugins/infra/public/pages/logs/settings/indices_configuration_form_state.ts b/x-pack/plugins/infra/public/pages/logs/settings/indices_configuration_form_state.ts index 1a70aaff6636c..31f4b96c92379 100644 --- a/x-pack/plugins/infra/public/pages/logs/settings/indices_configuration_form_state.ts +++ b/x-pack/plugins/infra/public/pages/logs/settings/indices_configuration_form_state.ts @@ -14,7 +14,7 @@ import { LogIndexPatternReference, } from '../../../../common/log_sources'; import { useKibanaIndexPatternService } from '../../../hooks/use_kibana_index_patterns'; -import { useCompositeFormElement, useFormElement } from './form_elements'; +import { useFormElement } from './form_elements'; import { FormValidationError, validateIndexPattern, @@ -80,44 +80,3 @@ export const useLogIndicesFormElement = (initialValue: LogIndicesFormState) => { return logIndicesFormElement; }; - -export interface FieldsFormState { - tiebreakerField: string; - timestampField: string; -} - -export const useFieldsFormElement = (initialValues: FieldsFormState) => { - const tiebreakerFieldFormElement = useFormElement({ - initialValue: initialValues.tiebreakerField, - validate: useMemo( - () => async (tiebreakerField) => validateStringNotEmpty('tiebreaker', tiebreakerField), - [] - ), - }); - - const timestampFieldFormElement = useFormElement({ - initialValue: initialValues.timestampField, - validate: useMemo( - () => async (timestampField) => validateStringNotEmpty('timestamp', timestampField), - [] - ), - }); - - const fieldsFormElement = useCompositeFormElement( - useMemo( - () => ({ - childFormElements: { - tiebreaker: tiebreakerFieldFormElement, - timestamp: timestampFieldFormElement, - }, - }), - [tiebreakerFieldFormElement, timestampFieldFormElement] - ) - ); - - return { - fieldsFormElement, - tiebreakerFieldFormElement, - timestampFieldFormElement, - }; -}; diff --git a/x-pack/plugins/infra/public/pages/logs/settings/indices_configuration_panel.stories.tsx b/x-pack/plugins/infra/public/pages/logs/settings/indices_configuration_panel.stories.tsx index 546bb9aab0f33..cf0f302136fbf 100644 --- a/x-pack/plugins/infra/public/pages/logs/settings/indices_configuration_panel.stories.tsx +++ b/x-pack/plugins/infra/public/pages/logs/settings/indices_configuration_panel.stories.tsx @@ -15,12 +15,7 @@ import { MockIndexPatternsKibanaContextProvider, MockIndexPatternSpec, } from '../../../hooks/use_kibana_index_patterns.mock'; -import { - FieldsFormState, - LogIndicesFormState, - useFieldsFormElement, - useLogIndicesFormElement, -} from './indices_configuration_form_state'; +import { LogIndicesFormState, useLogIndicesFormElement } from './indices_configuration_form_state'; import { IndicesConfigurationPanel } from './indices_configuration_panel'; export default { @@ -69,17 +64,14 @@ type IndicesConfigurationPanelStoryArgs = Pick< > & { availableIndexPatterns: MockIndexPatternSpec[]; logIndices: LogIndicesFormState; - fields: FieldsFormState; }; const IndicesConfigurationPanelTemplate: Story = ({ isLoading, isReadOnly, logIndices, - fields, }) => { const logIndicesFormElement = useLogIndicesFormElement(logIndices); - const { tiebreakerFieldFormElement, timestampFieldFormElement } = useFieldsFormElement(fields); return ( <> @@ -87,8 +79,6 @@ const IndicesConfigurationPanelTemplate: Story // field states{'\n'} @@ -98,14 +88,6 @@ const IndicesConfigurationPanelTemplate: Story; - tiebreakerFieldFormElement: FormElement; - timestampFieldFormElement: FormElement; -}>( - ({ - isLoading, - isReadOnly, - indicesFormElement, - tiebreakerFieldFormElement, - timestampFieldFormElement, - }) => { - const trackSwitchToIndexPatternReference = useUiTracker({ app: 'infra_logs' }); +}>(({ isLoading, isReadOnly, indicesFormElement }) => { + const trackSwitchToIndexPatternReference = useUiTracker({ app: 'infra_logs' }); - const switchToIndexPatternReference = useCallback(() => { - indicesFormElement.updateValue(() => undefined); - trackSwitchToIndexPatternReference({ - metric: 'configuration_switch_to_index_pattern_reference', - }); - }, [indicesFormElement, trackSwitchToIndexPatternReference]); + const switchToIndexPatternReference = useCallback(() => { + indicesFormElement.updateValue(() => undefined); + trackSwitchToIndexPatternReference({ + metric: 'configuration_switch_to_index_pattern_reference', + }); + }, [indicesFormElement, trackSwitchToIndexPatternReference]); - if (isIndexPatternFormElement(indicesFormElement)) { - return ( - + ); + } else if (isIndexNamesFormElement(indicesFormElement)) { + return ( + <> + - ); - } else if (isIndexNamesFormElement(indicesFormElement)) { - return ( - <> - - - - ); - } else { - return null; - } + + ); + } else { + return null; } -); +}); const isIndexPatternFormElement = isFormElementForType( (value): value is LogIndexPatternReference | undefined => diff --git a/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_form_state.tsx b/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_form_state.tsx index ac390a5bcfb8b..6523708d18ee0 100644 --- a/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_form_state.tsx +++ b/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_form_state.tsx @@ -8,7 +8,7 @@ import { useMemo } from 'react'; import { LogSourceConfigurationProperties } from '../../../containers/logs/log_source'; import { useCompositeFormElement } from './form_elements'; -import { useFieldsFormElement, useLogIndicesFormElement } from './indices_configuration_form_state'; +import { useLogIndicesFormElement } from './indices_configuration_form_state'; import { useLogColumnsFormElement } from './log_columns_configuration_form_state'; import { useNameFormElement } from './name_configuration_form_state'; @@ -28,17 +28,6 @@ export const useLogSourceConfigurationFormState = ( ) ); - const { fieldsFormElement, tiebreakerFieldFormElement, timestampFieldFormElement } = - useFieldsFormElement( - useMemo( - () => ({ - tiebreakerField: configuration?.fields?.tiebreaker ?? '_doc', - timestampField: configuration?.fields?.timestamp ?? '@timestamp', - }), - [configuration] - ) - ); - const logColumnsFormElement = useLogColumnsFormElement( useMemo(() => configuration?.logColumns ?? [], [configuration]) ); @@ -49,12 +38,11 @@ export const useLogSourceConfigurationFormState = ( childFormElements: { name: nameFormElement, logIndices: logIndicesFormElement, - fields: fieldsFormElement, logColumns: logColumnsFormElement, }, validate: async () => [], }), - [nameFormElement, logIndicesFormElement, fieldsFormElement, logColumnsFormElement] + [nameFormElement, logIndicesFormElement, logColumnsFormElement] ) ); @@ -64,7 +52,5 @@ export const useLogSourceConfigurationFormState = ( logColumnsFormElement, nameFormElement, sourceConfigurationFormElement, - tiebreakerFieldFormElement, - timestampFieldFormElement, }; }; diff --git a/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx b/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx index 883c321db9ae6..b7fbef74781fc 100644 --- a/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx +++ b/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx @@ -67,8 +67,6 @@ export const LogsSettingsPage = () => { logIndicesFormElement, logColumnsFormElement, nameFormElement, - tiebreakerFieldFormElement, - timestampFieldFormElement, } = useLogSourceConfigurationFormState(source?.configuration); const persistUpdates = useCallback(async () => { @@ -113,8 +111,6 @@ export const LogsSettingsPage = () => { isLoading={isLoading} isReadOnly={!isWriteable} indicesFormElement={logIndicesFormElement} - tiebreakerFieldFormElement={tiebreakerFieldFormElement} - timestampFieldFormElement={timestampFieldFormElement} /> diff --git a/x-pack/plugins/infra/public/pages/metrics/settings/fields_configuration_panel.tsx b/x-pack/plugins/infra/public/pages/metrics/settings/fields_configuration_panel.tsx deleted file mode 100644 index 1c6c627c08d0b..0000000000000 --- a/x-pack/plugins/infra/public/pages/metrics/settings/fields_configuration_panel.tsx +++ /dev/null @@ -1,274 +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 { - EuiDescribedFormGroup, - EuiCode, - EuiFieldText, - EuiForm, - EuiFormRow, - EuiSpacer, - EuiTitle, - EuiCallOut, - EuiLink, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; -import { InputFieldProps } from './input_fields'; - -interface FieldsConfigurationPanelProps { - containerFieldProps: InputFieldProps; - hostFieldProps: InputFieldProps; - isLoading: boolean; - readOnly: boolean; - podFieldProps: InputFieldProps; - timestampFieldProps: InputFieldProps; -} - -export const FieldsConfigurationPanel = ({ - containerFieldProps, - hostFieldProps, - isLoading, - readOnly, - podFieldProps, - timestampFieldProps, -}: FieldsConfigurationPanelProps) => { - const isHostValueDefault = hostFieldProps.value === 'host.name'; - const isContainerValueDefault = containerFieldProps.value === 'container.id'; - const isPodValueDefault = podFieldProps.value === 'kubernetes.pod.uid'; - const isTimestampValueDefault = timestampFieldProps.value === '@timestamp'; - return ( - - -

- -

-
- - -

- - - - ), - ecsLink: ( - - ECS - - ), - }} - /> -

-
- - - - - } - description={ - - } - > - @timestamp, - }} - /> - } - isInvalid={timestampFieldProps.isInvalid} - label={ - - } - > - - - - - - - } - description={ - - } - > - container.id, - }} - /> - } - isInvalid={containerFieldProps.isInvalid} - label={ - - } - > - - - - - - - } - description={ - - } - > - host.name, - }} - /> - } - isInvalid={hostFieldProps.isInvalid} - label={ - - } - > - - - - - - - } - description={ - - } - > - kubernetes.pod.uid, - }} - /> - } - isInvalid={podFieldProps.isInvalid} - label={ - - } - > - - - -
- ); -}; diff --git a/x-pack/plugins/infra/public/pages/metrics/settings/indices_configuration_form_state.ts b/x-pack/plugins/infra/public/pages/metrics/settings/indices_configuration_form_state.ts index ced87112e6e9a..359ada83b2ffa 100644 --- a/x-pack/plugins/infra/public/pages/metrics/settings/indices_configuration_form_state.ts +++ b/x-pack/plugins/infra/public/pages/metrics/settings/indices_configuration_form_state.ts @@ -16,11 +16,6 @@ interface FormState { name: string; description: string; metricAlias: string; - containerField: string; - hostField: string; - podField: string; - tiebreakerField: string; - timestampField: string; anomalyThreshold: number; } @@ -63,59 +58,7 @@ export const useIndicesConfigurationFormState = ({ }), [formState.metricAlias] ); - const containerFieldFieldProps = useMemo( - () => - createInputFieldProps({ - errors: validateInputFieldNotEmpty(formState.containerField), - name: `containerField`, - onChange: (containerField) => - setFormStateChanges((changes) => ({ ...changes, containerField })), - value: formState.containerField, - }), - [formState.containerField] - ); - const hostFieldFieldProps = useMemo( - () => - createInputFieldProps({ - errors: validateInputFieldNotEmpty(formState.hostField), - name: `hostField`, - onChange: (hostField) => setFormStateChanges((changes) => ({ ...changes, hostField })), - value: formState.hostField, - }), - [formState.hostField] - ); - const podFieldFieldProps = useMemo( - () => - createInputFieldProps({ - errors: validateInputFieldNotEmpty(formState.podField), - name: `podField`, - onChange: (podField) => setFormStateChanges((changes) => ({ ...changes, podField })), - value: formState.podField, - }), - [formState.podField] - ); - const tiebreakerFieldFieldProps = useMemo( - () => - createInputFieldProps({ - errors: validateInputFieldNotEmpty(formState.tiebreakerField), - name: `tiebreakerField`, - onChange: (tiebreakerField) => - setFormStateChanges((changes) => ({ ...changes, tiebreakerField })), - value: formState.tiebreakerField, - }), - [formState.tiebreakerField] - ); - const timestampFieldFieldProps = useMemo( - () => - createInputFieldProps({ - errors: validateInputFieldNotEmpty(formState.timestampField), - name: `timestampField`, - onChange: (timestampField) => - setFormStateChanges((changes) => ({ ...changes, timestampField })), - value: formState.timestampField, - }), - [formState.timestampField] - ); + const anomalyThresholdFieldProps = useMemo( () => createInputRangeFieldProps({ @@ -132,23 +75,9 @@ export const useIndicesConfigurationFormState = ({ () => ({ name: nameFieldProps, metricAlias: metricAliasFieldProps, - containerField: containerFieldFieldProps, - hostField: hostFieldFieldProps, - podField: podFieldFieldProps, - tiebreakerField: tiebreakerFieldFieldProps, - timestampField: timestampFieldFieldProps, anomalyThreshold: anomalyThresholdFieldProps, }), - [ - nameFieldProps, - metricAliasFieldProps, - containerFieldFieldProps, - hostFieldFieldProps, - podFieldFieldProps, - tiebreakerFieldFieldProps, - timestampFieldFieldProps, - anomalyThresholdFieldProps, - ] + [nameFieldProps, metricAliasFieldProps, anomalyThresholdFieldProps] ); const errors = useMemo( @@ -179,10 +108,5 @@ const defaultFormState: FormState = { name: '', description: '', metricAlias: '', - containerField: '', - hostField: '', - podField: '', - tiebreakerField: '', - timestampField: '', anomalyThreshold: 0, }; diff --git a/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_form_state.tsx b/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_form_state.tsx index 909bf294e4098..d97d66cd5c05d 100644 --- a/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_form_state.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_form_state.tsx @@ -20,11 +20,6 @@ export const useSourceConfigurationFormState = ( name: configuration.name, description: configuration.description, metricAlias: configuration.metricAlias, - containerField: configuration.fields.container, - hostField: configuration.fields.host, - podField: configuration.fields.pod, - tiebreakerField: configuration.fields.tiebreaker, - timestampField: configuration.fields.timestamp, anomalyThreshold: configuration.anomalyThreshold, } : undefined, @@ -56,13 +51,6 @@ export const useSourceConfigurationFormState = ( name: indicesConfigurationFormState.formState.name, description: indicesConfigurationFormState.formState.description, metricAlias: indicesConfigurationFormState.formState.metricAlias, - fields: { - container: indicesConfigurationFormState.formState.containerField, - host: indicesConfigurationFormState.formState.hostField, - pod: indicesConfigurationFormState.formState.podField, - tiebreaker: indicesConfigurationFormState.formState.tiebreakerField, - timestamp: indicesConfigurationFormState.formState.timestampField, - }, anomalyThreshold: indicesConfigurationFormState.formState.anomalyThreshold, }), [indicesConfigurationFormState.formState] @@ -73,13 +61,6 @@ export const useSourceConfigurationFormState = ( name: indicesConfigurationFormState.formStateChanges.name, description: indicesConfigurationFormState.formStateChanges.description, metricAlias: indicesConfigurationFormState.formStateChanges.metricAlias, - fields: { - container: indicesConfigurationFormState.formStateChanges.containerField, - host: indicesConfigurationFormState.formStateChanges.hostField, - pod: indicesConfigurationFormState.formStateChanges.podField, - tiebreaker: indicesConfigurationFormState.formStateChanges.tiebreakerField, - timestamp: indicesConfigurationFormState.formStateChanges.timestampField, - }, anomalyThreshold: indicesConfigurationFormState.formStateChanges.anomalyThreshold, }), [indicesConfigurationFormState.formStateChanges] diff --git a/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_settings.tsx b/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_settings.tsx index 0adf4ed6b5b45..f10bd7cf9e38a 100644 --- a/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_settings.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_settings.tsx @@ -20,7 +20,6 @@ import { SourceLoadingPage } from '../../../components/source_loading_page'; import { Source } from '../../../containers/metrics_source'; import { useInfraMLCapabilitiesContext } from '../../../containers/ml/infra_ml_capabilities'; import { Prompt } from '../../../utils/navigation_warning_prompt'; -import { FieldsConfigurationPanel } from './fields_configuration_panel'; import { IndicesConfigurationPanel } from './indices_configuration_panel'; import { MLConfigurationPanel } from './ml_configuration_panel'; import { NameConfigurationPanel } from './name_configuration_panel'; @@ -123,17 +122,6 @@ export const SourceConfigurationSettings = ({ /> - - - - {hasInfraMLCapabilities && ( <> diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index a31bef7c9c214..5e769882a2793 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -14,6 +14,7 @@ import { AvgIndexPatternColumn, MedianIndexPatternColumn, PercentileIndexPatternColumn, + LastValueIndexPatternColumn, OperationType, PersistedIndexPatternLayer, RangeIndexPatternColumn, @@ -219,6 +220,15 @@ export class LensAttributes { columnFilter, }); } + if (operationType === 'last_value') { + return this.getLastValueOperationColumn({ + sourceField, + operationType, + label, + seriesConfig, + columnFilter, + }); + } if (operationType?.includes('th')) { return this.getPercentileNumberColumn(sourceField, operationType, seriesConfig!); } @@ -226,6 +236,36 @@ export class LensAttributes { return this.getNumberRangeColumn(sourceField, seriesConfig!, label); } + getLastValueOperationColumn({ + sourceField, + label, + seriesConfig, + operationType, + columnFilter, + }: { + sourceField: string; + operationType: 'last_value'; + label?: string; + seriesConfig: SeriesConfig; + columnFilter?: ColumnFilter; + }): LastValueIndexPatternColumn { + return { + ...buildNumberColumn(sourceField), + operationType, + label: i18n.translate('xpack.observability.expView.columns.operation.label', { + defaultMessage: '{operationType} of {sourceField}', + values: { + sourceField: label || seriesConfig.labels[sourceField], + operationType: capitalize(operationType), + }, + }), + filter: columnFilter, + params: { + sortField: '@timestamp', + }, + }; + } + getNumberOperationColumn({ sourceField, label, @@ -622,6 +662,12 @@ export class LensAttributes { const label = timeShift ? `${mainYAxis.label}(${timeShift})` : mainYAxis.label; + let filterQuery = columnFilter || mainYAxis.filter?.query; + + if (columnFilter && mainYAxis.filter?.query) { + filterQuery = `${columnFilter} and ${mainYAxis.filter.query}`; + } + layers[layerId] = { columnOrder: [ `x-axis-column-${layerId}`, @@ -637,9 +683,7 @@ export class LensAttributes { ...mainYAxis, label, filter: { - query: mainYAxis.filter - ? `${columnFilter} and ${mainYAxis.filter.query}` - : columnFilter, + query: filterQuery ?? '', language: 'kuery', }, ...(timeShift ? { timeShift } : {}), 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 8951ffcda63d8..e548ec2714e14 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 @@ -111,44 +111,48 @@ export function getSyntheticsKPIConfig({ indexPattern }: ConfigProps): SeriesCon field: SYNTHETICS_LCP, id: SYNTHETICS_LCP, columnType: OPERATION_COLUMN, - columnFilters: [STEP_METRIC_FILTER], + columnFilters: getStepMetricColumnFilter(SYNTHETICS_LCP), }, { label: FCP_LABEL, field: SYNTHETICS_FCP, id: SYNTHETICS_FCP, columnType: OPERATION_COLUMN, - columnFilters: [STEP_METRIC_FILTER], + columnFilters: getStepMetricColumnFilter(SYNTHETICS_FCP), }, { label: DCL_LABEL, field: SYNTHETICS_DCL, id: SYNTHETICS_DCL, columnType: OPERATION_COLUMN, - columnFilters: [STEP_METRIC_FILTER], + columnFilters: getStepMetricColumnFilter(SYNTHETICS_DCL), }, { label: DOCUMENT_ONLOAD_LABEL, field: SYNTHETICS_DOCUMENT_ONLOAD, id: SYNTHETICS_DOCUMENT_ONLOAD, columnType: OPERATION_COLUMN, - columnFilters: [STEP_METRIC_FILTER], + columnFilters: getStepMetricColumnFilter(SYNTHETICS_DOCUMENT_ONLOAD), }, { label: CLS_LABEL, field: SYNTHETICS_CLS, id: SYNTHETICS_CLS, columnType: OPERATION_COLUMN, - columnFilters: [STEP_METRIC_FILTER], + columnFilters: getStepMetricColumnFilter(SYNTHETICS_CLS), }, ], labels: { ...FieldLabels, [SUMMARY_UP]: UP_LABEL, [SUMMARY_DOWN]: DOWN_LABEL }, }; } -const STEP_METRIC_FILTER: ColumnFilter = { - language: 'kuery', - query: `synthetics.type: step/metrics`, +const getStepMetricColumnFilter = (field: string): ColumnFilter[] => { + return [ + { + language: 'kuery', + query: `synthetics.type: step/metrics and ${field}: *`, + }, + ]; }; const STEP_END_FILTER: ColumnFilter = { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/embeddable.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/embeddable.tsx index 10ec4075a8155..35ce2fc6c1a47 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/embeddable.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/embeddable.tsx @@ -12,7 +12,7 @@ import { AllSeries, useTheme } from '../../../..'; import { LayerConfig, LensAttributes } from '../configurations/lens_attributes'; import { ReportViewType } from '../types'; import { getLayerConfigs } from '../hooks/use_lens_attributes'; -import { LensPublicStart } from '../../../../../../lens/public'; +import { LensPublicStart, XYState } from '../../../../../../lens/public'; import { OperationTypeComponent } from '../series_editor/columns/operation_type_select'; import { IndexPatternState } from '../hooks/use_app_index_pattern'; @@ -22,6 +22,8 @@ export interface ExploratoryEmbeddableProps { appendTitle?: JSX.Element; title: string | JSX.Element; showCalculationMethod?: boolean; + axisTitlesVisibility?: XYState['axisTitlesVisibilitySettings']; + legendIsVisible?: boolean; } export interface ExploratoryEmbeddableComponentProps extends ExploratoryEmbeddableProps { @@ -37,6 +39,8 @@ export default function Embeddable({ appendTitle, indexPatterns, lens, + axisTitlesVisibility, + legendIsVisible, showCalculationMethod = false, }: ExploratoryEmbeddableComponentProps) { const LensComponent = lens?.EmbeddableComponent; @@ -57,11 +61,20 @@ export default function Embeddable({ return No lens component; } + const attributesJSON = lensAttributes.getJSON(); + + (attributesJSON.state.visualization as XYState).axisTitlesVisibilitySettings = + axisTitlesVisibility; + + if (typeof legendIsVisible !== 'undefined') { + (attributesJSON.state.visualization as XYState).legend.isVisible = legendIsVisible; + } + return ( - + - +

{title}

@@ -81,7 +94,7 @@ export default function Embeddable({ id="exploratoryView" style={{ height: '100%' }} timeRange={series?.time} - attributes={lensAttributes.getJSON()} + attributes={attributesJSON} onBrushEnd={({ range }) => {}} />
@@ -92,7 +105,7 @@ const Wrapper = styled.div` height: 100%; &&& { > :nth-child(2) { - height: calc(100% - 56px); + height: calc(100% - 32px); } } `; 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 6d83e25cc96e3..a223a74d74aea 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 @@ -73,6 +73,12 @@ export function OperationTypeComponent({ defaultMessage: 'Sum', }), }, + { + value: 'last_value' as OperationType, + inputDisplay: i18n.translate('xpack.observability.expView.operationType.lastValue', { + defaultMessage: 'Last value', + }), + }, { value: '75th' as OperationType, inputDisplay: i18n.translate('xpack.observability.expView.operationType.75thPercentile', { diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_actions.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_actions.ts index 246cf059f4cbd..5cc564ee3d41d 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_actions.ts @@ -66,7 +66,11 @@ export const indexFleetActionsForHost = async ( const actionResponse = fleetActionGenerator.generateResponse({ action_id: action.action_id, agent_id: agentId, - action_data: action.data, + action_data: { + ...action.data, + // add ack to 4/5th of fleet response + ack: fleetActionGenerator.randomFloat() < 0.8 ? true : undefined, + }, }); esClient diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts index fb29297eb5929..d7ad417fc7d3f 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -64,6 +64,7 @@ export interface LogsEndpointActionResponse { export interface EndpointActionData { command: ISOLATION_ACTIONS; comment?: string; + ack?: boolean; } export interface EndpointAction { diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/building_block_alerts.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/building_block_alerts.spec.ts index 262ffe8163e57..94418e61b4053 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/building_block_alerts.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/building_block_alerts.spec.ts @@ -8,19 +8,23 @@ import { getBuildingBlockRule } from '../../objects/rule'; import { OVERVIEW_ALERTS_HISTOGRAM } from '../../screens/overview'; import { OVERVIEW } from '../../screens/security_header'; +import { waitForAlertsIndexToBeCreated, waitForAlertsPanelToBeLoaded } from '../../tasks/alerts'; import { goToRuleDetails } from '../../tasks/alerts_detection_rules'; import { createCustomRuleActivated } from '../../tasks/api_calls/rules'; import { cleanKibana } from '../../tasks/common'; import { waitForAlertsToPopulate, waitForTheRuleToBeExecuted } from '../../tasks/create_new_rule'; -import { loginAndWaitForPage } from '../../tasks/login'; +import { loginAndWaitForPage, loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { navigateFromHeaderTo } from '../../tasks/security_header'; -import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; +import { ALERTS_URL, DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; const EXPECTED_NUMBER_OF_ALERTS = 16; describe('Alerts generated by building block rules', () => { beforeEach(() => { cleanKibana(); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); }); it('Alerts should be visible on the Rule Detail page and not visible on the Overview page', () => { diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx index eb606cd8ff583..8e972b92c2fa1 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx @@ -16,6 +16,7 @@ import { useSourcererScope, getScopeFromPath } from '../../../../common/containe import { TimelineId } from '../../../../../common/types/timeline'; import { AutoSaveWarningMsg } from '../../../../timelines/components/timeline/auto_save_warning'; import { Flyout } from '../../../../timelines/components/flyout'; +import { useResolveRedirect } from '../../../../common/hooks/use_resolve_redirect'; export const BOTTOM_BAR_CLASSNAME = 'timeline-bottom-bar'; @@ -26,6 +27,7 @@ export const SecuritySolutionBottomBar = React.memo( const [showTimeline] = useShowTimeline(); const { indicesExist } = useSourcererScope(getScopeFromPath(pathname)); + useResolveRedirect(); return indicesExist && showTimeline ? ( <> diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/index.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/index.tsx index 281db88ebd057..2e04bbc5f1daf 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/index.tsx @@ -25,6 +25,7 @@ export const UseUrlStateMemo = React.memo( prevProps.pathName === nextProps.pathName && deepEqual(prevProps.urlState, nextProps.urlState) && deepEqual(prevProps.indexPattern, nextProps.indexPattern) && + prevProps.search === nextProps.search && deepEqual(prevProps.navTabs, nextProps.navTabs) ); 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 4a448e9064090..f04cf30da61f5 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 @@ -124,7 +124,7 @@ export const useSetInitialStateFromUrl = () => { [dispatch, updateTimeline, updateTimelineIsLoading] ); - return setInitialStateFromUrl; + return Object.freeze({ setInitialStateFromUrl, updateTimeline, updateTimelineIsLoading }); }; const updateTimerange = (newUrlStateString: string, dispatch: Dispatch) => { diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/query_timeline_by_id_on_url_change.test.ts b/x-pack/plugins/security_solution/public/common/components/url_state/query_timeline_by_id_on_url_change.test.ts new file mode 100644 index 0000000000000..5cc4f8e8b80f9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/url_state/query_timeline_by_id_on_url_change.test.ts @@ -0,0 +1,140 @@ +/* + * Copyright 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 { queryTimelineById } from '../../../timelines/components/open_timeline/helpers'; +import { queryTimelineByIdOnUrlChange } from './query_timeline_by_id_on_url_change'; +import * as urlHelpers from './helpers'; + +jest.mock('../../../timelines/components/open_timeline/helpers'); + +describe('queryTimelineByIdOnUrlChange', () => { + const oldTestTimelineId = '04e8ffb0-2c2a-11ec-949c-39005af91f70'; + const newTestTimelineId = `${oldTestTimelineId}-newId`; + const oldTimelineRisonSearchString = `?timeline=(activeTab:query,graphEventId:%27%27,id:%27${oldTestTimelineId}%27,isOpen:!t)`; + const newTimelineRisonSearchString = `?timeline=(activeTab:query,graphEventId:%27%27,id:%27${newTestTimelineId}%27,isOpen:!t)`; + const mockUpdateTimeline = jest.fn(); + const mockUpdateTimelineIsLoading = jest.fn(); + const mockQueryTimelineById = jest.fn(); + beforeEach(() => { + (queryTimelineById as jest.Mock).mockImplementation(mockQueryTimelineById); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('when search strings are empty', () => { + it('should not call queryTimelineById', () => { + queryTimelineByIdOnUrlChange({ + oldSearch: '', + search: '', + timelineIdFromReduxStore: 'current-timeline-id', + updateTimeline: mockUpdateTimeline, + updateTimelineIsLoading: mockUpdateTimelineIsLoading, + }); + + expect(queryTimelineById).not.toBeCalled(); + }); + }); + + describe('when search string has not changed', () => { + it('should not call queryTimelineById', () => { + queryTimelineByIdOnUrlChange({ + oldSearch: oldTimelineRisonSearchString, + search: oldTimelineRisonSearchString, + timelineIdFromReduxStore: 'timeline-id', + updateTimeline: mockUpdateTimeline, + updateTimelineIsLoading: mockUpdateTimelineIsLoading, + }); + + expect(queryTimelineById).not.toBeCalled(); + }); + }); + + describe('when decode rison fails', () => { + it('should not call queryTimelineById', () => { + jest.spyOn(urlHelpers, 'decodeRisonUrlState').mockImplementationOnce(() => { + throw new Error('Unable to decode'); + }); + + queryTimelineByIdOnUrlChange({ + oldSearch: oldTimelineRisonSearchString, + search: newTimelineRisonSearchString, + timelineIdFromReduxStore: '', + updateTimeline: mockUpdateTimeline, + updateTimelineIsLoading: mockUpdateTimelineIsLoading, + }); + + expect(queryTimelineById).not.toBeCalled(); + }); + }); + + describe('when new id is not provided', () => { + it('should not call queryTimelineById', () => { + queryTimelineByIdOnUrlChange({ + oldSearch: oldTimelineRisonSearchString, + search: '?timeline=(activeTab:query)', // no id + timelineIdFromReduxStore: newTestTimelineId, + updateTimeline: mockUpdateTimeline, + updateTimelineIsLoading: mockUpdateTimelineIsLoading, + }); + + expect(queryTimelineById).not.toBeCalled(); + }); + }); + + describe('when new id matches the data in redux', () => { + it('should not call queryTimelineById', () => { + queryTimelineByIdOnUrlChange({ + oldSearch: oldTimelineRisonSearchString, + search: newTimelineRisonSearchString, + timelineIdFromReduxStore: newTestTimelineId, + updateTimeline: mockUpdateTimeline, + updateTimelineIsLoading: mockUpdateTimelineIsLoading, + }); + + expect(queryTimelineById).not.toBeCalled(); + }); + }); + + // You can only redirect or run into conflict scenarios when already viewing a timeline + describe('when not actively on a page with timeline in the search field', () => { + it('should not call queryTimelineById', () => { + queryTimelineByIdOnUrlChange({ + oldSearch: '?random=foo', + search: newTimelineRisonSearchString, + timelineIdFromReduxStore: oldTestTimelineId, + updateTimeline: mockUpdateTimeline, + updateTimelineIsLoading: mockUpdateTimelineIsLoading, + }); + + expect(queryTimelineById).not.toBeCalled(); + }); + }); + + describe('when an old timeline id exists, but a new id is given', () => { + it('should call queryTimelineById', () => { + queryTimelineByIdOnUrlChange({ + oldSearch: oldTimelineRisonSearchString, + search: newTimelineRisonSearchString, + timelineIdFromReduxStore: oldTestTimelineId, + updateTimeline: mockUpdateTimeline, + updateTimelineIsLoading: mockUpdateTimelineIsLoading, + }); + + expect(queryTimelineById).toBeCalledWith({ + activeTimelineTab: 'query', + duplicate: false, + graphEventId: '', + timelineId: newTestTimelineId, + openTimeline: true, + updateIsLoading: mockUpdateTimelineIsLoading, + updateTimeline: mockUpdateTimeline, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/query_timeline_by_id_on_url_change.ts b/x-pack/plugins/security_solution/public/common/components/url_state/query_timeline_by_id_on_url_change.ts new file mode 100644 index 0000000000000..2778cefdc7953 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/url_state/query_timeline_by_id_on_url_change.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Action } from 'typescript-fsa'; +import { DispatchUpdateTimeline } from '../../../timelines/components/open_timeline/types'; +import { queryTimelineById } from '../../../timelines/components/open_timeline/helpers'; +import { TimelineTabs } from '../../../../common/types/timeline'; +import { + decodeRisonUrlState, + getQueryStringFromLocation, + getParamFromQueryString, +} from './helpers'; +import { TimelineUrl } from '../../../timelines/store/timeline/model'; +import { CONSTANTS } from './constants'; + +const getQueryStringKeyValue = ({ search, urlKey }: { search: string; urlKey: string }) => + getParamFromQueryString(getQueryStringFromLocation(search), urlKey); + +interface QueryTimelineIdOnUrlChange { + oldSearch?: string; + search: string; + timelineIdFromReduxStore: string; + updateTimeline: DispatchUpdateTimeline; + updateTimelineIsLoading: (status: { id: string; isLoading: boolean }) => Action<{ + id: string; + isLoading: boolean; + }>; +} + +/** + * After the initial load of the security solution, timeline is not updated when the timeline url search value is changed + * This is because those state changes happen in place and doesn't lead to a requerying of data for the new id. + * To circumvent this for the sake of the redirects needed for the saved object Id changes happening in 8.0 + * We are actively pulling the id changes that take place for timeline in the url and calling the query below + * to request the new data. + */ +export const queryTimelineByIdOnUrlChange = ({ + oldSearch, + search, + timelineIdFromReduxStore, + updateTimeline, + updateTimelineIsLoading, +}: QueryTimelineIdOnUrlChange) => { + const oldUrlStateString = getQueryStringKeyValue({ + urlKey: CONSTANTS.timeline, + search: oldSearch ?? '', + }); + + const newUrlStateString = getQueryStringKeyValue({ urlKey: CONSTANTS.timeline, search }); + + if (oldUrlStateString != null && newUrlStateString != null) { + let newTimeline = null; + let oldTimeline = null; + try { + newTimeline = decodeRisonUrlState(newUrlStateString); + } catch (error) { + // do nothing as timeline is defaulted to null + } + + try { + oldTimeline = decodeRisonUrlState(oldUrlStateString); + } catch (error) { + // do nothing as timeline is defaulted to null + } + const newId = newTimeline?.id; + const oldId = oldTimeline?.id; + + if (newId && newId !== oldId && newId !== timelineIdFromReduxStore) { + queryTimelineById({ + activeTimelineTab: newTimeline?.activeTab ?? TimelineTabs.query, + duplicate: false, + graphEventId: newTimeline?.graphEventId, + timelineId: newId, + openTimeline: true, + updateIsLoading: updateTimelineIsLoading, + updateTimeline, + }); + } + } +}; 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 e803c091423be..06ed33ac69f6e 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 @@ -79,6 +79,7 @@ export interface PreviousLocationUrlState { pathName: string | undefined; pageName: string | undefined; urlState: UrlState; + search: string | undefined; } export interface UrlStateToRedux { diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx index bc47ba9d8ae99..3245d647227ad 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx @@ -39,6 +39,7 @@ import { } from './types'; import { TimelineUrl } from '../../../timelines/store/timeline/model'; import { UrlInputsModel } from '../../store/inputs/model'; +import { queryTimelineByIdOnUrlChange } from './query_timeline_by_id_on_url_change'; function usePrevious(value: PreviousLocationUrlState) { const ref = useRef(value); @@ -60,9 +61,10 @@ export const useUrlStateHooks = ({ const [isFirstPageLoad, setIsFirstPageLoad] = useState(true); const { filterManager, savedQueries } = useKibana().services.data.query; const { pathname: browserPathName } = useLocation(); - const prevProps = usePrevious({ pathName, pageName, urlState }); + const prevProps = usePrevious({ pathName, pageName, urlState, search }); - const setInitialStateFromUrl = useSetInitialStateFromUrl(); + const { setInitialStateFromUrl, updateTimeline, updateTimelineIsLoading } = + useSetInitialStateFromUrl(); const handleInitialize = useCallback( (type: UrlStateType) => { @@ -190,6 +192,16 @@ export const useUrlStateHooks = ({ document.title = `${getTitle(pageName, navTabs)} - Kibana`; }, [pageName, navTabs]); + useEffect(() => { + queryTimelineByIdOnUrlChange({ + oldSearch: prevProps.search, + search, + timelineIdFromReduxStore: urlState.timeline.id, + updateTimeline, + updateTimelineIsLoading, + }); + }, [search, prevProps.search, urlState.timeline.id, updateTimeline, updateTimelineIsLoading]); + return null; }; diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_resolve_conflict.test.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_resolve_conflict.test.tsx new file mode 100644 index 0000000000000..bafbe078cdbdb --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_resolve_conflict.test.tsx @@ -0,0 +1,166 @@ +/* + * Copyright 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 { useLocation } from 'react-router-dom'; +import { renderHook } from '@testing-library/react-hooks'; +import { useDeepEqualSelector } from './use_selector'; +import { useKibana } from '../lib/kibana'; +import { useResolveConflict } from './use_resolve_conflict'; +import * as urlHelpers from '../components/url_state/helpers'; + +jest.mock('react-router-dom', () => { + const original = jest.requireActual('react-router-dom'); + + return { + ...original, + useLocation: jest.fn(), + }; +}); +jest.mock('../lib/kibana'); +jest.mock('./use_selector'); +jest.mock('../../timelines/store/timeline/', () => ({ + timelineSelectors: { + getTimelineByIdSelector: () => jest.fn(), + }, +})); + +describe('useResolveConflict', () => { + const mockGetLegacyUrlConflict = jest.fn().mockReturnValue('Test!'); + beforeEach(() => { + jest.resetAllMocks(); + // Mock rison format in actual url + (useLocation as jest.Mock).mockReturnValue({ + pathname: 'my/cool/path', + search: + 'timeline=(activeTab:query,graphEventId:%27%27,id:%2704e8ffb0-2c2a-11ec-949c-39005af91f70%27,isOpen:!t)', + }); + (useKibana as jest.Mock).mockReturnValue({ + services: { + spaces: { + ui: { + components: { + getLegacyUrlConflict: mockGetLegacyUrlConflict, + }, + }, + }, + }, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('resolve object is not provided', () => { + it('should not show the conflict message', async () => { + (useDeepEqualSelector as jest.Mock).mockImplementation(() => ({ + savedObjectId: 'current-saved-object-id', + activeTab: 'some-tab', + graphEventId: 'current-graph-event-id', + show: false, + })); + const { result } = renderHook<{}, JSX.Element | null>(() => useResolveConflict()); + expect(mockGetLegacyUrlConflict).not.toHaveBeenCalled(); + expect(result.current).toEqual(null); + }); + }); + + describe('outcome is exactMatch', () => { + it('should not show the conflict message', async () => { + (useDeepEqualSelector as jest.Mock).mockImplementation(() => ({ + resolveTimelineConfig: { + outcome: 'exactMatch', + }, + savedObjectId: 'current-saved-object-id', + activeTab: 'some-tab', + graphEventId: 'current-graph-event-id', + show: false, + })); + const { result } = renderHook<{}, JSX.Element | null>(() => useResolveConflict()); + expect(mockGetLegacyUrlConflict).not.toHaveBeenCalled(); + expect(result.current).toEqual(null); + }); + }); + + describe('outcome is aliasMatch', () => { + it('should not show the conflict message', async () => { + (useDeepEqualSelector as jest.Mock).mockImplementation(() => ({ + resolveTimelineConfig: { + outcome: 'aliasMatch', + alias_target_id: 'new-id', + }, + })); + const { result } = renderHook<{}, JSX.Element | null>(() => useResolveConflict()); + expect(mockGetLegacyUrlConflict).not.toHaveBeenCalled(); + expect(result.current).toEqual(null); + }); + }); + + describe('outcome is conflict', () => { + const mockTextContent = 'I am the visible conflict message'; + it('should show the conflict message', async () => { + (useDeepEqualSelector as jest.Mock).mockImplementation(() => ({ + resolveTimelineConfig: { + outcome: 'conflict', + alias_target_id: 'new-id', + }, + })); + mockGetLegacyUrlConflict.mockImplementation(() => mockTextContent); + const { result } = renderHook<{}, JSX.Element | null>(() => useResolveConflict()); + expect(mockGetLegacyUrlConflict).toHaveBeenCalledWith({ + objectNoun: 'timeline', + currentObjectId: '04e8ffb0-2c2a-11ec-949c-39005af91f70', + otherObjectId: 'new-id', + otherObjectPath: + 'my/cool/path?timeline=%28activeTab%3Aquery%2CgraphEventId%3A%27%27%2Cid%3Anew-id%2CisOpen%3A%21t%29', + }); + expect(result.current).toMatchInlineSnapshot(` + + I am the visible conflict message + + + `); + }); + + describe('rison is unable to be decoded', () => { + it('should use timeline values from redux to create the otherObjectPath', async () => { + jest.spyOn(urlHelpers, 'decodeRisonUrlState').mockImplementation(() => { + throw new Error('Unable to decode'); + }); + (useLocation as jest.Mock).mockReturnValue({ + pathname: 'my/cool/path', + search: '?foo=bar', + }); + (useDeepEqualSelector as jest.Mock).mockImplementation(() => ({ + resolveTimelineConfig: { + outcome: 'conflict', + alias_target_id: 'new-id', + }, + savedObjectId: 'current-saved-object-id', + activeTab: 'some-tab', + graphEventId: 'current-graph-event-id', + show: false, + })); + mockGetLegacyUrlConflict.mockImplementation(() => mockTextContent); + renderHook(() => useResolveConflict()); + const { result } = renderHook<{}, JSX.Element | null>(() => useResolveConflict()); + expect(mockGetLegacyUrlConflict).toHaveBeenCalledWith({ + objectNoun: 'timeline', + currentObjectId: 'current-saved-object-id', + otherObjectId: 'new-id', + otherObjectPath: + 'my/cool/path?foo=bar&timeline=%28activeTab%3Asome-tab%2CgraphEventId%3Acurrent-graph-event-id%2Cid%3Anew-id%2CisOpen%3A%21f%29', + }); + expect(result.current).toMatchInlineSnapshot(` + + I am the visible conflict message + + + `); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_resolve_conflict.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_resolve_conflict.tsx new file mode 100644 index 0000000000000..6a493d944ecda --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_resolve_conflict.tsx @@ -0,0 +1,100 @@ +/* + * Copyright 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, { useCallback, useMemo } from 'react'; +import { useLocation } from 'react-router-dom'; +import { EuiSpacer } from '@elastic/eui'; +import { useDeepEqualSelector } from './use_selector'; +import { TimelineId } from '../../../common/types/timeline'; +import { timelineSelectors } from '../../timelines/store/timeline'; +import { TimelineUrl } from '../../timelines/store/timeline/model'; +import { timelineDefaults } from '../../timelines/store/timeline/defaults'; +import { decodeRisonUrlState, encodeRisonUrlState } from '../components/url_state/helpers'; +import { useKibana } from '../lib/kibana'; +import { CONSTANTS } from '../components/url_state/constants'; + +/** + * Unfortunately the url change initiated when clicking the button to otherObjectPath doesn't seem to be + * respected by the useSetInitialStateFromUrl here: x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx + * + * FYI: It looks like the routing causes replaceStateInLocation to be called instead: + * x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts + * + * Potentially why the markdown component needs a click handler as well for timeline? + * see: /x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/timeline/processor.tsx + */ +export const useResolveConflict = () => { + const { search, pathname } = useLocation(); + const { spaces } = useKibana().services; + const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); + const { resolveTimelineConfig, savedObjectId, show, graphEventId, activeTab } = + useDeepEqualSelector((state) => getTimeline(state, TimelineId.active) ?? timelineDefaults); + + const getLegacyUrlConflictCallout = useCallback(() => { + // This function returns a callout component *if* we have encountered a "legacy URL conflict" scenario + if ( + !spaces || + resolveTimelineConfig?.outcome !== 'conflict' || + resolveTimelineConfig?.alias_target_id == null + ) { + return null; + } + + const searchQuery = new URLSearchParams(search); + const timelineRison = searchQuery.get(CONSTANTS.timeline) ?? undefined; + // Try to get state on URL, but default to what's in Redux in case of decodeRisonFailure + const currentTimelineState = { + id: savedObjectId ?? '', + isOpen: !!show, + activeTab, + graphEventId, + }; + let timelineSearch: TimelineUrl = currentTimelineState; + try { + timelineSearch = decodeRisonUrlState(timelineRison) ?? currentTimelineState; + } catch (error) { + // do nothing as it's already defaulted on line 77 + } + // We have resolved to one object, but another object has a legacy URL alias associated with this ID/page. We should display a + // callout with a warning for the user, and provide a way for them to navigate to the other object. + const currentObjectId = timelineSearch?.id; + const newSavedObjectId = resolveTimelineConfig?.alias_target_id ?? ''; // This is always defined if outcome === 'conflict' + + const newTimelineSearch: TimelineUrl = { + ...timelineSearch, + id: newSavedObjectId, + }; + const newTimelineRison = encodeRisonUrlState(newTimelineSearch); + searchQuery.set(CONSTANTS.timeline, newTimelineRison); + + const newPath = `${pathname}?${searchQuery.toString()}${window.location.hash}`; + + return ( + <> + {spaces.ui.components.getLegacyUrlConflict({ + objectNoun: CONSTANTS.timeline, + currentObjectId, + otherObjectId: newSavedObjectId, + otherObjectPath: newPath, + })} + + + ); + }, [ + activeTab, + graphEventId, + pathname, + resolveTimelineConfig?.alias_target_id, + resolveTimelineConfig?.outcome, + savedObjectId, + search, + show, + spaces, + ]); + + return useMemo(() => getLegacyUrlConflictCallout(), [getLegacyUrlConflictCallout]); +}; diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_resolve_redirect.test.ts b/x-pack/plugins/security_solution/public/common/hooks/use_resolve_redirect.test.ts new file mode 100644 index 0000000000000..c9a0eedefd0af --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_resolve_redirect.test.ts @@ -0,0 +1,140 @@ +/* + * Copyright 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 { useLocation } from 'react-router-dom'; +import { renderHook } from '@testing-library/react-hooks'; +import { useDeepEqualSelector } from './use_selector'; +import { useKibana } from '../lib/kibana'; +import { useResolveRedirect } from './use_resolve_redirect'; +import * as urlHelpers from '../components/url_state/helpers'; + +jest.mock('react-router-dom', () => { + const original = jest.requireActual('react-router-dom'); + + return { + ...original, + useLocation: jest.fn(), + }; +}); +jest.mock('../lib/kibana'); +jest.mock('./use_selector'); +jest.mock('../../timelines/store/timeline/', () => ({ + timelineSelectors: { + getTimelineByIdSelector: () => jest.fn(), + }, +})); + +describe('useResolveRedirect', () => { + const mockRedirectLegacyUrl = jest.fn(); + beforeEach(() => { + jest.resetAllMocks(); + // Mock rison format in actual url + (useLocation as jest.Mock).mockReturnValue({ + pathname: 'my/cool/path', + search: + 'timeline=(activeTab:query,graphEventId:%27%27,id:%2704e8ffb0-2c2a-11ec-949c-39005af91f70%27,isOpen:!t)', + }); + (useKibana as jest.Mock).mockReturnValue({ + services: { + spaces: { + ui: { + redirectLegacyUrl: mockRedirectLegacyUrl, + }, + }, + }, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('resolve object is not provided', () => { + it('should not redirect', async () => { + (useDeepEqualSelector as jest.Mock).mockImplementation(() => ({ + savedObjectId: 'current-saved-object-id', + activeTab: 'some-tab', + graphEventId: 'current-graph-event-id', + show: false, + })); + renderHook(() => useResolveRedirect()); + expect(mockRedirectLegacyUrl).not.toHaveBeenCalled(); + }); + }); + + describe('outcome is exactMatch', () => { + it('should not redirect', async () => { + (useDeepEqualSelector as jest.Mock).mockImplementation(() => ({ + resolveTimelineConfig: { + outcome: 'exactMatch', + }, + savedObjectId: 'current-saved-object-id', + activeTab: 'some-tab', + graphEventId: 'current-graph-event-id', + show: false, + })); + renderHook(() => useResolveRedirect()); + expect(mockRedirectLegacyUrl).not.toHaveBeenCalled(); + }); + }); + + describe('outcome is aliasMatch', () => { + it('should redirect to url with id:new-id if outcome is aliasMatch', async () => { + (useDeepEqualSelector as jest.Mock).mockImplementation(() => ({ + resolveTimelineConfig: { + outcome: 'aliasMatch', + alias_target_id: 'new-id', + }, + })); + renderHook(() => useResolveRedirect()); + expect(mockRedirectLegacyUrl).toHaveBeenCalledWith( + 'my/cool/path?timeline=%28activeTab%3Aquery%2CgraphEventId%3A%27%27%2Cid%3Anew-id%2CisOpen%3A%21t%29', + 'timeline' + ); + }); + + describe('rison is unable to be decoded', () => { + it('should use timeline values from redux to create the redirect path', async () => { + jest.spyOn(urlHelpers, 'decodeRisonUrlState').mockImplementation(() => { + throw new Error('Unable to decode'); + }); + (useLocation as jest.Mock).mockReturnValue({ + pathname: 'my/cool/path', + search: '?foo=bar', + }); + (useDeepEqualSelector as jest.Mock).mockImplementation(() => ({ + resolveTimelineConfig: { + outcome: 'aliasMatch', + alias_target_id: 'new-id', + }, + savedObjectId: 'current-saved-object-id', + activeTab: 'some-tab', + graphEventId: 'current-graph-event-id', + show: false, + })); + renderHook(() => useResolveRedirect()); + expect(mockRedirectLegacyUrl).toHaveBeenCalledWith( + 'my/cool/path?foo=bar&timeline=%28activeTab%3Asome-tab%2CgraphEventId%3Acurrent-graph-event-id%2Cid%3Anew-id%2CisOpen%3A%21f%29', + 'timeline' + ); + }); + }); + }); + + describe('outcome is conflict', () => { + it('should not redirect', async () => { + (useDeepEqualSelector as jest.Mock).mockImplementation(() => ({ + resolveTimelineConfig: { + outcome: 'conflict', + alias_target_id: 'new-id', + }, + })); + renderHook(() => useResolveRedirect()); + expect(mockRedirectLegacyUrl).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_resolve_redirect.ts b/x-pack/plugins/security_solution/public/common/hooks/use_resolve_redirect.ts new file mode 100644 index 0000000000000..a6ba0b24828e7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_resolve_redirect.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 { useCallback, useEffect, useMemo, useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import { useDeepEqualSelector } from './use_selector'; +import { TimelineId } from '../../../common/types/timeline'; +import { timelineSelectors } from '../../timelines/store/timeline/'; +import { timelineDefaults } from '../../timelines/store/timeline/defaults'; +import { decodeRisonUrlState, encodeRisonUrlState } from '../components/url_state/helpers'; +import { useKibana } from '../lib/kibana'; +import { TimelineUrl } from '../../timelines/store/timeline/model'; +import { CONSTANTS } from '../components/url_state/constants'; + +/** + * This hooks is specifically for use with the resolve api that was introduced as part of 7.16 + * If a deep link id has been migrated to a new id, this hook will cause a redirect to a url with + * the new ID. + */ + +export const useResolveRedirect = () => { + const { search, pathname } = useLocation(); + const [hasRedirected, updateHasRedirected] = useState(false); + const { spaces } = useKibana().services; + + const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); + const { resolveTimelineConfig, savedObjectId, show, activeTab, graphEventId } = + useDeepEqualSelector((state) => getTimeline(state, TimelineId.active) ?? timelineDefaults); + + const redirect = useCallback(() => { + const searchQuery = new URLSearchParams(search); + const timelineRison = searchQuery.get(CONSTANTS.timeline) ?? undefined; + + // Try to get state on URL, but default to what's in Redux in case of decodeRisonFailure + const currentTimelineState = { + id: savedObjectId ?? '', + isOpen: !!show, + activeTab, + graphEventId, + }; + let timelineSearch: TimelineUrl = currentTimelineState; + try { + timelineSearch = decodeRisonUrlState(timelineRison) ?? currentTimelineState; + } catch (error) { + // do nothing as it's already defaulted on line 77 + } + + if ( + hasRedirected || + !spaces || + resolveTimelineConfig?.outcome !== 'aliasMatch' || + resolveTimelineConfig?.alias_target_id == null + ) { + return null; + } + + // We found this object by a legacy URL alias from its old ID; redirect the user to the page with its new ID, preserving any URL hash + const newObjectId = resolveTimelineConfig?.alias_target_id ?? ''; // This is always defined if outcome === 'aliasMatch' + const newTimelineSearch = { + ...timelineSearch, + id: newObjectId, + }; + const newTimelineRison = encodeRisonUrlState(newTimelineSearch); + searchQuery.set(CONSTANTS.timeline, newTimelineRison); + const newPath = `${pathname}?${searchQuery.toString()}`; + spaces.ui.redirectLegacyUrl(newPath, CONSTANTS.timeline); + // Prevent the effect from being called again as the url change takes place in location rather than a true redirect + updateHasRedirected(true); + }, [ + activeTab, + graphEventId, + hasRedirected, + pathname, + resolveTimelineConfig?.outcome, + resolveTimelineConfig?.alias_target_id, + savedObjectId, + search, + show, + spaces, + ]); + + useEffect(() => { + redirect(); + }, [redirect]); +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/__mocks__/index.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/__mocks__/index.ts index f1e1c42539eff..2521d14481ca8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/__mocks__/index.ts @@ -9,7 +9,7 @@ import { TimelineStatus, TimelineType } from '../../../../../common/types/timeli export const mockTimeline = { data: { - getOneTimeline: { + timeline: { savedObjectId: 'eb2781c0-1df5-11eb-8589-2f13958b79f7', columns: [ { @@ -163,6 +163,7 @@ export const mockTimeline = { version: 'WzQ4NSwxXQ==', __typename: 'TimelineResult', }, + outcome: 'exactMatch', }, loading: false, networkStatus: 7, @@ -171,7 +172,7 @@ export const mockTimeline = { export const mockTemplate = { data: { - getOneTimeline: { + timeline: { savedObjectId: '0c70a200-1de0-11eb-885c-6fc13fca1850', columns: [ { @@ -416,6 +417,7 @@ export const mockTemplate = { version: 'WzQwMywxXQ==', __typename: 'TimelineResult', }, + outcome: 'exactMatch', }, loading: false, networkStatus: 7, 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 5d52d2c8a4d48..1b93f1556a95c 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 @@ -50,7 +50,7 @@ import { mockTimeline as mockSelectedTimeline, mockTemplate as mockSelectedTemplate, } from './__mocks__'; -import { getTimeline } from '../../containers/api'; +import { resolveTimeline } from '../../containers/api'; import { defaultHeaders } from '../timeline/body/column_headers/default_headers'; jest.mock('../../../common/store/inputs/actions'); @@ -951,7 +951,7 @@ describe('helpers', () => { }; beforeAll(async () => { - (getTimeline as jest.Mock).mockRejectedValue(mockError); + (resolveTimeline as jest.Mock).mockRejectedValue(mockError); queryTimelineById<{}>(args as unknown as QueryTimelineById<{}>); }); @@ -986,7 +986,7 @@ describe('helpers', () => { }; beforeAll(async () => { - (getTimeline as jest.Mock).mockResolvedValue(selectedTimeline); + (resolveTimeline as jest.Mock).mockResolvedValue(selectedTimeline); await queryTimelineById<{}>(args as unknown as QueryTimelineById<{}>); }); @@ -1002,7 +1002,7 @@ describe('helpers', () => { }); test('get timeline by Id', () => { - expect(getTimeline).toHaveBeenCalled(); + expect(resolveTimeline).toHaveBeenCalled(); }); test('it does not call onError when an error does not occur', () => { @@ -1011,7 +1011,7 @@ describe('helpers', () => { test('Do not override daterange if TimelineStatus is active', () => { const { timeline } = formatTimelineResultToModel( - omitTypenameInTimeline(getOr({}, 'data.getOneTimeline', selectedTimeline)), + omitTypenameInTimeline(getOr({}, 'data.timeline', selectedTimeline)), args.duplicate, args.timelineType ); @@ -1044,7 +1044,7 @@ describe('helpers', () => { }; beforeAll(async () => { - (getTimeline as jest.Mock).mockResolvedValue(selectedTimeline); + (resolveTimeline as jest.Mock).mockResolvedValue(selectedTimeline); await queryTimelineById<{}>(args as unknown as QueryTimelineById<{}>); }); @@ -1060,12 +1060,12 @@ describe('helpers', () => { }); test('get timeline by Id', () => { - expect(getTimeline).toHaveBeenCalled(); + expect(resolveTimeline).toHaveBeenCalled(); }); test('should not override daterange if TimelineStatus is active', () => { const { timeline } = formatTimelineResultToModel( - omitTypenameInTimeline(getOr({}, 'data.getOneTimeline', selectedTimeline)), + omitTypenameInTimeline(getOr({}, 'data.timeline', selectedTimeline)), args.duplicate, args.timelineType ); @@ -1085,6 +1085,10 @@ describe('helpers', () => { to: '2020-07-08T08:20:18.966Z', notes: [], id: TimelineId.active, + resolveTimelineConfig: { + outcome: 'exactMatch', + alias_target_id: undefined, + }, }); }); @@ -1112,12 +1116,12 @@ describe('helpers', () => { }; beforeAll(async () => { - (getTimeline as jest.Mock).mockResolvedValue(template); + (resolveTimeline as jest.Mock).mockResolvedValue(template); await queryTimelineById<{}>(args as unknown as QueryTimelineById<{}>); }); afterAll(() => { - (getTimeline as jest.Mock).mockReset(); + (resolveTimeline as jest.Mock).mockReset(); jest.clearAllMocks(); }); @@ -1129,12 +1133,12 @@ describe('helpers', () => { }); test('get timeline by Id', () => { - expect(getTimeline).toHaveBeenCalled(); + expect(resolveTimeline).toHaveBeenCalled(); }); test('override daterange if TimelineStatus is immutable', () => { const { timeline } = formatTimelineResultToModel( - omitTypenameInTimeline(getOr({}, 'data.getOneTimeline', template)), + omitTypenameInTimeline(getOr({}, 'data.timeline', template)), args.duplicate, args.timelineType ); 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 1fbddf61f8cd3..f325ab34e88d5 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 @@ -20,6 +20,7 @@ import { TimelineType, TimelineTabs, TimelineResult, + SingleTimelineResolveResponse, ColumnHeaderResult, FilterTimelineResult, DataProviderResult, @@ -65,7 +66,7 @@ import { DEFAULT_FROM_MOMENT, DEFAULT_TO_MOMENT, } from '../../../common/utils/default_date_settings'; -import { getTimeline } from '../../containers/api'; +import { resolveTimeline } from '../../containers/api'; import { PinnedEvent } from '../../../../common/types/timeline/pinned_event'; import { NoteResult } from '../../../../common/types/timeline/note'; @@ -346,11 +347,12 @@ export const queryTimelineById = ({ updateTimeline, }: QueryTimelineById) => { updateIsLoading({ id: TimelineId.active, isLoading: true }); - Promise.resolve(getTimeline(timelineId)) + Promise.resolve(resolveTimeline(timelineId)) .then((result) => { - const timelineToOpen: TimelineResult = omitTypenameInTimeline( - getOr({}, 'data.getOneTimeline', result) - ); + const data: SingleTimelineResolveResponse['data'] | null = getOr(null, 'data', result); + if (!data) return; + + const timelineToOpen = omitTypenameInTimeline(data.timeline); const { timeline, notes } = formatTimelineResultToModel( timelineToOpen, @@ -370,6 +372,10 @@ export const queryTimelineById = ({ from, id: TimelineId.active, notes, + resolveTimelineConfig: { + outcome: data.outcome, + alias_target_id: data.alias_target_id, + }, timeline: { ...timeline, activeTab: activeTimelineTab, @@ -399,6 +405,7 @@ export const dispatchUpdateTimeline = forceNotes = false, from, notes, + resolveTimelineConfig, timeline, to, ruleNote, @@ -429,7 +436,9 @@ export const dispatchUpdateTimeline = } else { dispatch(dispatchSetTimelineRangeDatePicker({ from, to })); } - dispatch(dispatchAddTimeline({ id, timeline, savedTimeline: duplicate })); + dispatch( + dispatchAddTimeline({ id, timeline, resolveTimelineConfig, savedTimeline: duplicate }) + ); if ( timeline.kqlQuery != null && timeline.kqlQuery.filterQuery != null && diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts index 79a700856c00f..4c9ce991252dc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts @@ -16,6 +16,7 @@ import { TemplateTimelineTypeLiteral, RowRendererId, TimelineStatusLiteralWithNull, + SingleTimelineResolveResponse, } from '../../../../common/types/timeline'; /** The users who added a timeline to favorites */ @@ -194,12 +195,17 @@ export interface OpenTimelineProps { hideActions?: ActionTimelineToShow[]; } +export interface ResolveTimelineConfig { + alias_target_id: SingleTimelineResolveResponse['data']['alias_target_id']; + outcome: SingleTimelineResolveResponse['data']['outcome']; +} export interface UpdateTimeline { duplicate: boolean; id: string; forceNotes?: boolean; from: string; notes: NoteResult[] | null | undefined; + resolveTimelineConfig?: ResolveTimelineConfig; timeline: TimelineModel; to: string; ruleNote?: string; @@ -210,6 +216,7 @@ export type DispatchUpdateTimeline = ({ id, from, notes, + resolveTimelineConfig, timeline, to, ruleNote, 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 db7a3cc3c9900..c91673e5f931c 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 @@ -40,6 +40,12 @@ const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock; jest.mock('use-resize-observer/polyfilled'); mockUseResizeObserver.mockImplementation(() => ({})); +jest.mock('../../../common/hooks/use_resolve_conflict', () => { + return { + useResolveConflict: jest.fn().mockImplementation(() => null), + }; +}); + jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); 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 a199dd5aa55f8..ca883529b5ce6 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 @@ -28,6 +28,7 @@ import { TabsContent } from './tabs_content'; import { HideShowContainer, TimelineContainer } from './styles'; import { useTimelineFullScreen } from '../../../common/containers/use_full_screen'; import { EXIT_FULL_SCREEN_CLASS_NAME } from '../../../common/components/exit_full_screen'; +import { useResolveConflict } from '../../../common/hooks/use_resolve_conflict'; const TimelineTemplateBadge = styled.div` background: ${({ theme }) => theme.eui.euiColorVis3_behindText}; @@ -119,6 +120,7 @@ const StatefulTimelineComponent: React.FC = ({ [containerElement, onSkipFocusBeforeEventsTable, onSkipFocusAfterEventsTable] ); const timelineContext = useMemo(() => ({ timelineId }), [timelineId]); + const resolveConflictComponent = useResolveConflict(); return ( @@ -132,7 +134,7 @@ const StatefulTimelineComponent: React.FC = ({ {timelineType === TimelineType.template && ( {i18n.TIMELINE_TEMPLATE} )} - + {resolveConflictComponent} span { + .euiTab__content { display: flex; flex-direction: row; white-space: pre; diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.ts index 7f74912be09b4..44a750cc7283b 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/api.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/api.ts @@ -338,19 +338,6 @@ export const getTimelineTemplate = async (templateTimelineId: string) => { return decodeSingleTimelineResponse(response); }; -export const getResolvedTimelineTemplate = async (templateTimelineId: string) => { - const response = await KibanaServices.get().http.get( - TIMELINE_RESOLVE_URL, - { - query: { - template_timeline_id: templateTimelineId, - }, - } - ); - - return decodeResolvedSingleTimelineResponse(response); -}; - export const getAllTimelines = async (args: GetTimelinesArgs, abortSignal: AbortSignal) => { const response = await KibanaServices.get().http.fetch(TIMELINES_URL, { method: 'GET', 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 95ad6c5d44ca3..f3a70bd1390ae 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 @@ -25,6 +25,7 @@ import type { SerializedFilterQuery, } from '../../../../common/types/timeline'; import { tGridActions } from '../../../../../timelines/public'; +import { ResolveTimelineConfig } from '../../components/open_timeline/types'; export const { applyDeltaToColumnWidth, clearEventsDeleted, @@ -91,6 +92,7 @@ export const updateTimeline = actionCreator<{ export const addTimeline = actionCreator<{ id: string; timeline: TimelineModel; + resolveTimelineConfig?: ResolveTimelineConfig; savedTimeline?: boolean; }>('ADD_TIMELINE'); 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 4691872bfb927..4c2b8d2992d3d 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 @@ -14,64 +14,65 @@ import { SubsetTimelineModel, TimelineModel } from './model'; // normalizeTimeRange uses getTimeRangeSettings which cannot be used outside Kibana context if the uiSettings is not false const { from: start, to: end } = normalizeTimeRange({ from: '', to: '' }, false); -export const timelineDefaults: SubsetTimelineModel & Pick = - { - activeTab: TimelineTabs.query, - prevActiveTab: TimelineTabs.query, - columns: defaultHeaders, - documentType: '', - defaultColumns: defaultHeaders, - dataProviders: [], - dateRange: { start, end }, - deletedEventIds: [], - description: '', - eqlOptions: { - eventCategoryField: 'event.category', - tiebreakerField: '', - timestampField: '@timestamp', - query: '', - size: 100, +export const timelineDefaults: SubsetTimelineModel & + Pick = { + activeTab: TimelineTabs.query, + prevActiveTab: TimelineTabs.query, + columns: defaultHeaders, + documentType: '', + defaultColumns: defaultHeaders, + dataProviders: [], + dateRange: { start, end }, + deletedEventIds: [], + description: '', + eqlOptions: { + eventCategoryField: 'event.category', + tiebreakerField: '', + timestampField: '@timestamp', + query: '', + size: 100, + }, + eventType: 'all', + eventIdToNoteIds: {}, + excludedRowRendererIds: [], + expandedDetail: {}, + highlightedDropAndProviderId: '', + historyIds: [], + filters: [], + indexNames: [], + isFavorite: false, + isLive: false, + isSelectAllChecked: false, + isLoading: false, + isSaving: false, + itemsPerPage: 25, + itemsPerPageOptions: [10, 25, 50, 100], + kqlMode: 'filter', + kqlQuery: { + filterQuery: null, + }, + loadingEventIds: [], + resolveTimelineConfig: undefined, + queryFields: [], + title: '', + timelineType: TimelineType.default, + templateTimelineId: null, + templateTimelineVersion: null, + noteIds: [], + pinnedEventIds: {}, + pinnedEventsSaveObject: {}, + savedObjectId: null, + selectAll: false, + selectedEventIds: {}, + show: false, + showCheckboxes: false, + sort: [ + { + columnId: '@timestamp', + columnType: 'number', + sortDirection: 'desc', }, - eventType: 'all', - eventIdToNoteIds: {}, - excludedRowRendererIds: [], - expandedDetail: {}, - highlightedDropAndProviderId: '', - historyIds: [], - filters: [], - indexNames: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - isSaving: false, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50, 100], - kqlMode: 'filter', - kqlQuery: { - filterQuery: null, - }, - loadingEventIds: [], - queryFields: [], - title: '', - timelineType: TimelineType.default, - templateTimelineId: null, - templateTimelineVersion: null, - noteIds: [], - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - savedObjectId: null, - selectAll: false, - selectedEventIds: {}, - show: false, - showCheckboxes: false, - sort: [ - { - columnId: '@timestamp', - columnType: 'number', - sortDirection: 'desc', - }, - ], - status: TimelineStatus.draft, - version: null, - }; + ], + status: TimelineStatus.draft, + version: null, +}; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts index 6ee844958aeed..b7af561ae2a04 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts @@ -47,6 +47,7 @@ import { RESIZED_COLUMN_MIN_WITH, } from '../../components/timeline/body/constants'; import { activeTimeline } from '../../containers/active_timeline_context'; +import { ResolveTimelineConfig } from '../../components/open_timeline/types'; export const isNotNull = (value: T | null): value is T => value !== null; @@ -124,6 +125,7 @@ export const addTimelineNoteToEvent = ({ interface AddTimelineParams { id: string; + resolveTimelineConfig?: ResolveTimelineConfig; timeline: TimelineModel; timelineById: TimelineById; } @@ -145,6 +147,7 @@ export const shouldResetActiveTimelineContext = ( */ export const addTimelineToStore = ({ id, + resolveTimelineConfig, timeline, timelineById, }: AddTimelineParams): TimelineById => { @@ -159,6 +162,7 @@ export const addTimelineToStore = ({ filterManager: timelineById[id].filterManager, isLoading: timelineById[id].isLoading, initialized: timelineById[id].initialized, + resolveTimelineConfig, dateRange: timeline.status === TimelineStatus.immutable && timeline.timelineType === TimelineType.template 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 b53da997c08cb..29b49197ef797 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 @@ -15,6 +15,7 @@ import type { } from '../../../../common/types/timeline'; import { PinnedEvent } from '../../../../common/types/timeline/pinned_event'; import type { TGridModelForTimeline } from '../../../../../timelines/public'; +import { ResolveTimelineConfig } from '../../components/open_timeline/types'; export const DEFAULT_PAGE_COUNT = 2; // Eui Pager will not render unless this is a minimum of 2 pages export type KqlMode = 'filter' | 'search'; @@ -59,6 +60,7 @@ export type TimelineModel = TGridModelForTimeline & { /** Events pinned to this timeline */ pinnedEventIds: Record; pinnedEventsSaveObject: Record; + resolveTimelineConfig?: ResolveTimelineConfig; showSaveModal?: boolean; savedQueryId?: string | null; /** When true, show the timeline flyover */ 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 97fa72667a3c6..e997bbd848d50 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 @@ -94,9 +94,14 @@ export const initialTimelineState: TimelineState = { /** The reducer for all timeline actions */ export const timelineReducer = reducerWithInitialState(initialTimelineState) - .case(addTimeline, (state, { id, timeline }) => ({ + .case(addTimeline, (state, { id, timeline, resolveTimelineConfig }) => ({ ...state, - timelineById: addTimelineToStore({ id, timeline, timelineById: state.timelineById }), + timelineById: addTimelineToStore({ + id, + timeline, + resolveTimelineConfig, + timelineById: state.timelineById, + }), })) .case(createTimeline, (state, { id, timelineType = TimelineType.default, ...timelineProps }) => { return { diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index bee0e9b3a3d1d..61813d1a122b4 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -9,7 +9,6 @@ import { CoreStart } from '../../../../src/core/public'; import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { EmbeddableStart } from '../../../../src/plugins/embeddable/public'; -import { SpacesPluginStart } from '../../../plugins/spaces/public'; import { LensPublicStart } from '../../../plugins/lens/public'; import { NewsfeedPublicPluginStart } from '../../../../src/plugins/newsfeed/public'; import { Start as InspectorStart } from '../../../../src/plugins/inspector/public'; @@ -18,6 +17,7 @@ import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/p import { Storage } from '../../../../src/plugins/kibana_utils/public'; import { FleetStart } from '../../fleet/public'; import { PluginStart as ListsPluginStart } from '../../lists/public'; +import { SpacesPluginStart } from '../../spaces/public'; import { TriggersAndActionsUIPublicPluginSetup as TriggersActionsSetup, TriggersAndActionsUIPublicPluginStart as TriggersActionsStart, diff --git a/x-pack/plugins/security_solution/server/config.ts b/x-pack/plugins/security_solution/server/config.ts index e0b8ad883f4a2..61cbb5641c5f6 100644 --- a/x-pack/plugins/security_solution/server/config.ts +++ b/x-pack/plugins/security_solution/server/config.ts @@ -112,7 +112,7 @@ export const configSchema = schema.object({ schema.literal(UnderlyingLogClient.eventLog), schema.literal(UnderlyingLogClient.savedObjects), ], - { defaultValue: UnderlyingLogClient.savedObjects } + { defaultValue: UnderlyingLogClient.eventLog } ), }), 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 d59ecb674196c..b25b599517300 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions.ts @@ -9,6 +9,7 @@ import { ElasticsearchClient, Logger } from 'kibana/server'; import { SearchHit, SearchResponse } from '@elastic/elasticsearch/api/types'; import { ApiResponse } from '@elastic/elasticsearch'; import { AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX } from '../../../../fleet/common'; +import { ENDPOINT_ACTION_RESPONSES_INDEX } from '../../../common/endpoint/constants'; import { SecuritySolutionRequestHandlerContext } from '../../types'; import { ActivityLog, @@ -146,6 +147,41 @@ const getActivityLog = async ({ return sortedData; }; +const hasAckInResponse = (response: EndpointActionResponse): boolean => { + return typeof response.action_data.ack !== 'undefined'; +}; + +// return TRUE if for given action_id/agent_id +// there is no doc in .logs-endpoint.action.response-default +const hasNoEndpointResponse = ({ + action, + agentId, + indexedActionIds, +}: { + action: EndpointAction; + agentId: string; + indexedActionIds: string[]; +}): boolean => { + return action.agents.includes(agentId) && !indexedActionIds.includes(action.action_id); +}; + +// return TRUE if for given action_id/agent_id +// there is no doc in .fleet-actions-results +const hasNoFleetResponse = ({ + action, + agentId, + agentResponses, +}: { + action: EndpointAction; + agentId: string; + agentResponses: EndpointActionResponse[]; +}): boolean => { + return ( + action.agents.includes(agentId) && + !agentResponses.map((e) => e.action_id).includes(action.action_id) + ); +}; + export const getPendingActionCounts = async ( esClient: ElasticsearchClient, metadataService: EndpointMetadataService, @@ -179,21 +215,45 @@ export const getPendingActionCounts = async ( .catch(catchAndWrapError); // retrieve any responses to those action IDs from these agents - const responses = await fetchActionResponseIds( + const responses = await fetchActionResponses( esClient, metadataService, recentActions.map((a) => a.action_id), agentIDs ); - const pending: EndpointPendingActions[] = []; + // + + const pending: EndpointPendingActions[] = []; for (const agentId of agentIDs) { - const responseIDsFromAgent = responses[agentId]; + const agentResponses = responses[agentId]; + + // get response actionIds for responses with ACKs + const ackResponseActionIdList: string[] = agentResponses + .filter(hasAckInResponse) + .map((response) => response.action_id); + + // actions Ids that are indexed in new response index + const indexedActionIds = await hasEndpointResponseDoc({ + agentId, + actionIds: ackResponseActionIdList, + esClient, + }); + + const pendingActions: EndpointAction[] = recentActions.filter((action) => { + return ackResponseActionIdList.includes(action.action_id) // if has ack + ? hasNoEndpointResponse({ action, agentId, indexedActionIds }) // then find responses in new index + : hasNoFleetResponse({ + // else use the legacy way + action, + agentId, + agentResponses, + }); + }); pending.push({ agent_id: agentId, - pending_actions: recentActions - .filter((a) => a.agents.includes(agentId) && !responseIDsFromAgent.includes(a.action_id)) + pending_actions: pendingActions .map((a) => a.data.command) .reduce((acc, cur) => { if (cur in acc) { @@ -209,6 +269,43 @@ export const getPendingActionCounts = async ( return pending; }; +/** + * Returns a boolean for search result + * + * @param esClient + * @param actionIds + * @param agentIds + */ +const hasEndpointResponseDoc = async ({ + actionIds, + agentId, + esClient, +}: { + actionIds: string[]; + agentId: string; + esClient: ElasticsearchClient; +}): Promise => { + const response = await esClient + .search( + { + index: ENDPOINT_ACTION_RESPONSES_INDEX, + body: { + query: { + bool: { + filter: [{ terms: { action_id: actionIds } }, { term: { agent_id: agentId } }], + }, + }, + }, + }, + { ignore: [404] } + ) + .then( + (result) => result.body?.hits?.hits?.map((a) => a._source?.EndpointActions.action_id) || [] + ) + .catch(catchAndWrapError); + return response.filter((action): action is string => action !== undefined); +}; + /** * Returns back a map of elastic Agent IDs to array of Action IDs that have received a response. * @@ -217,16 +314,19 @@ export const getPendingActionCounts = async ( * @param actionIds * @param agentIds */ -const fetchActionResponseIds = async ( +const fetchActionResponses = async ( esClient: ElasticsearchClient, metadataService: EndpointMetadataService, actionIds: string[], agentIds: string[] -): Promise> => { - const actionResponsesByAgentId: Record = agentIds.reduce((acc, agentId) => { - acc[agentId] = []; - return acc; - }, {} as Record); +): Promise> => { + const actionResponsesByAgentId: Record = agentIds.reduce( + (acc, agentId) => { + acc[agentId] = []; + return acc; + }, + {} as Record + ); const actionResponses = await esClient .search( @@ -255,7 +355,7 @@ const fetchActionResponseIds = async ( return actionResponsesByAgentId; } - // Get the latest docs from the metadata datastream for the Elastic Agent IDs in the action responses + // Get the latest docs from the metadata data-stream for the Elastic Agent IDs in the action responses // This will be used determine if we should withhold the action id from the returned list in cases where // the Endpoint might not yet have sent an updated metadata document (which would be representative of // the state of the endpoint post-action) @@ -288,7 +388,7 @@ const fetchActionResponseIds = async ( enoughTimeHasLapsed || lastEndpointMetadataEventTimestamp > actionCompletedAtTimestamp ) { - actionResponsesByAgentId[actionResponse.agent_id].push(actionResponse.action_id); + actionResponsesByAgentId[actionResponse.agent_id].push(actionResponse); } } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/constants.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/constants.ts index f09eb43bf15f1..55624b56e39a0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/constants.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/constants.ts @@ -5,7 +5,7 @@ * 2.0. */ -export const RULE_EXECUTION_LOG_PROVIDER = 'rule-execution.security'; +export const RULE_EXECUTION_LOG_PROVIDER = 'securitySolution.ruleExecution'; export const ALERT_SAVED_OBJECT_TYPE = 'alert'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/saved_objects_adapter/rule_status_saved_objects_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/saved_objects_adapter/rule_status_saved_objects_client.ts index 66b646e96ea53..0026bba24eebe 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/saved_objects_adapter/rule_status_saved_objects_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/saved_objects_adapter/rule_status_saved_objects_client.ts @@ -21,7 +21,7 @@ import { IRuleStatusSOAttributes } from '../../rules/types'; export interface RuleStatusSavedObjectsClient { find: ( - options?: Omit + options: Omit & { ruleId: string } ) => Promise>>; findBulk: (ids: string[], statusesPerId: number) => Promise; create: ( @@ -47,9 +47,14 @@ export const ruleStatusSavedObjectsClientFactory = ( savedObjectsClient: SavedObjectsClientContract ): RuleStatusSavedObjectsClient => ({ find: async (options) => { + const references = { + id: options.ruleId, + type: 'alert', + }; const result = await savedObjectsClient.find({ ...options, type: legacyRuleStatusSavedObjectType, + hasReference: references, }); return result.saved_objects; }, 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 9db7afce62ee4..70db3a768fdb1 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 @@ -53,8 +53,7 @@ export class SavedObjectsAdapter implements IRuleExecutionLogClient { perPage: logsCount, sortField: 'statusDate', sortOrder: 'desc', - search: ruleId, - searchFields: ['references.id'], + ruleId, }); } diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 5c87d58199df4..8f4d602e26461 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -1,4 +1,3 @@ - { "extends": "../../../tsconfig.base.json", "compilerOptions": { @@ -40,7 +39,7 @@ { "path": "../maps/tsconfig.json" }, { "path": "../ml/tsconfig.json" }, { "path": "../spaces/tsconfig.json" }, - { "path": "../security/tsconfig.json"}, - { "path": "../timelines/tsconfig.json"}, + { "path": "../security/tsconfig.json" }, + { "path": "../timelines/tsconfig.json" } ] } diff --git a/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.ts b/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.ts index 6e9891ecd6d65..d6eb92b944ce1 100644 --- a/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.ts +++ b/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.ts @@ -52,6 +52,15 @@ export function createTaskManagerUsageCollector( }, }, task_type_exclusion: excludeTaskTypes, + failed_tasks: Object.entries(lastMonitoredHealth?.stats.workload?.value.task_types!).reduce( + (numb, [key, val]) => { + if (val.status.failed !== undefined) { + numb += val.status.failed; + } + return numb; + }, + 0 + ), }; }, schema: { @@ -79,6 +88,7 @@ export function createTaskManagerUsageCollector( }, }, task_type_exclusion: { type: 'array', items: { type: 'keyword' } }, + failed_tasks: { type: 'long' }, }, }); } diff --git a/x-pack/plugins/task_manager/server/usage/types.ts b/x-pack/plugins/task_manager/server/usage/types.ts index 0acbfd1d4fab9..f9ac823a58124 100644 --- a/x-pack/plugins/task_manager/server/usage/types.ts +++ b/x-pack/plugins/task_manager/server/usage/types.ts @@ -30,4 +30,5 @@ export interface TaskManagerUsage { p99: number; }; }; + failed_tasks: number; } 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 5bb559c137390..12763e4e26e31 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -11,15 +11,6 @@ "count_total": { "type": "long" }, - "count_active_total": { - "type": "long" - }, - "count_active_alert_history_connectors": { - "type": "long", - "_meta": { - "description": "The total number of preconfigured alert history connectors used by rules." - } - }, "count_by_type": { "properties": { "DYNAMIC_KEY": { @@ -60,6 +51,15 @@ } } }, + "count_active_total": { + "type": "long" + }, + "count_active_alert_history_connectors": { + "type": "long", + "_meta": { + "description": "The total number of preconfigured alert history connectors used by rules." + } + }, "count_active_by_type": { "properties": { "DYNAMIC_KEY": { @@ -99,6 +99,34 @@ "type": "long" } } + }, + "count_active_email_connectors_by_service_type": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "exchange_server": { + "type": "long" + }, + "gmail": { + "type": "long" + }, + "outlook365": { + "type": "long" + }, + "elastic_cloud": { + "type": "long" + }, + "other": { + "type": "long" + }, + "ses": { + "type": "long" + } + } + }, + "count_actions_namespaces": { + "type": "long" } } }, @@ -321,6 +349,9 @@ "type": "long" } } + }, + "count_rules_namespaces": { + "type": "long" } } }, @@ -7277,6 +7308,9 @@ "items": { "type": "keyword" } + }, + "failed_tasks": { + "type": "long" } } }, diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts b/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts index 5371d7004a864..26d32b13eede7 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts +++ b/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts @@ -35,6 +35,11 @@ const DATA_GRID_HEIGHT_BY_PAGE_SIZE: { [key: number]: number } = { * * Please delete me and allow DataGrid to calculate its height when the bug is fixed. */ + +const dataGridRowHeight = 36; +const headerSectionHeight = 32; +const additionalFiltersHeight = 44; + export const useDataGridHeightHack = (pageSize: number, rowCount: number) => { const [height, setHeight] = useState(DATA_GRID_HEIGHT_BY_PAGE_SIZE[pageSize]); @@ -44,7 +49,11 @@ export const useDataGridHeightHack = (pageSize: number, rowCount: number) => { if (rowCount === pageSize) { setHeight(DATA_GRID_HEIGHT_BY_PAGE_SIZE[pageSize]); + } else if (rowCount <= pageSize) { + // This is unnecessary if we add rowCount > pageSize below + setHeight(dataGridRowHeight * rowCount + (headerSectionHeight + additionalFiltersHeight)); } else if ( + // rowCount > pageSize && // This will fix the issue but is always full height so has a lot of empty state gridVirtualized && gridVirtualized.children[0].clientHeight !== gridVirtualized.clientHeight // check if it has vertical scroll ) { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index f831f3b91eaa5..781ef74a872eb 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6788,7 +6788,6 @@ "xpack.apm.transactionActionMenu.viewSampleDocumentLinkLabel": "サンプルドキュメントを表示", "xpack.apm.transactionBreakdown.chartTitle": "スパンタイプ別時間", "xpack.apm.transactionDetails.clearSelectionAriaLabel": "選択した項目をクリア", - "xpack.apm.transactionDetails.distribution.errorTitle": "分布の取得中にエラーが発生しました", "xpack.apm.transactionDetails.distribution.panelTitle": "レイテンシ分布", "xpack.apm.transactionDetails.emptySelectionText": "クリックおよびドラッグして範囲を選択", "xpack.apm.transactionDetails.noTraceParentButtonTooltip": "トレースの親が見つかりませんでした", @@ -13695,20 +13694,9 @@ "xpack.infra.sourceConfiguration.anomalyThresholdLabel": "最低重要度スコア", "xpack.infra.sourceConfiguration.anomalyThresholdTitle": "異常重要度しきい値", "xpack.infra.sourceConfiguration.applySettingsButtonLabel": "適用", - "xpack.infra.sourceConfiguration.containerFieldDescription": "Docker コンテナーの識別に使用されるフィールドです", - "xpack.infra.sourceConfiguration.containerFieldLabel": "コンテナー ID", - "xpack.infra.sourceConfiguration.containerFieldRecommendedValue": "推奨値は {defaultValue} です", - "xpack.infra.sourceConfiguration.deprecationMessage": "これらのフィールドの構成は廃止予定です。8.0.0で削除されます。このアプリケーションは{ecsLink}で動作するように設計されています。{documentationLink}を使用するには、インデックスを調整してください。", - "xpack.infra.sourceConfiguration.deprecationNotice": "廃止通知", "xpack.infra.sourceConfiguration.discardSettingsButtonLabel": "破棄", - "xpack.infra.sourceConfiguration.documentedFields": "文書化されたフィールド", "xpack.infra.sourceConfiguration.fieldEmptyErrorMessage": "このフィールドは未入力のままにできません。", "xpack.infra.sourceConfiguration.fieldLogColumnTitle": "フィールド", - "xpack.infra.sourceConfiguration.fieldsSectionTitle": "フィールド", - "xpack.infra.sourceConfiguration.hostFieldDescription": "推奨値は {defaultValue} です", - "xpack.infra.sourceConfiguration.hostFieldLabel": "ホスト名", - "xpack.infra.sourceConfiguration.hostNameFieldDescription": "ホストの識別に使用されるフィールドです", - "xpack.infra.sourceConfiguration.hostNameFieldLabel": "ホスト名", "xpack.infra.sourceConfiguration.indicesSectionTitle": "インデックス", "xpack.infra.sourceConfiguration.logColumnsSectionTitle": "ログ列", "xpack.infra.sourceConfiguration.logIndicesDescription": "ログデータを含む一致するインデックスのインデックスパターンです", @@ -13726,17 +13714,8 @@ "xpack.infra.sourceConfiguration.nameSectionTitle": "名前", "xpack.infra.sourceConfiguration.noLogColumnsDescription": "上のボタンでこのリストに列を追加します。", "xpack.infra.sourceConfiguration.noLogColumnsTitle": "列がありません", - "xpack.infra.sourceConfiguration.podFieldDescription": "Kubernetes ポッドの識別に使用されるフィールドです", - "xpack.infra.sourceConfiguration.podFieldLabel": "ポッド ID", - "xpack.infra.sourceConfiguration.podFieldRecommendedValue": "推奨値は {defaultValue} です", "xpack.infra.sourceConfiguration.removeLogColumnButtonLabel": "{columnDescription} 列を削除", "xpack.infra.sourceConfiguration.systemColumnBadgeLabel": "システム", - "xpack.infra.sourceConfiguration.tiebreakerFieldDescription": "同じタイムスタンプの 2 つのエントリーを識別するのに使用されるフィールドです", - "xpack.infra.sourceConfiguration.tiebreakerFieldLabel": "タイブレーカー", - "xpack.infra.sourceConfiguration.tiebreakerFieldRecommendedValue": "推奨値は {defaultValue} です", - "xpack.infra.sourceConfiguration.timestampFieldDescription": "ログエントリーの並べ替えに使用されるタイムスタンプです", - "xpack.infra.sourceConfiguration.timestampFieldLabel": "タイムスタンプ", - "xpack.infra.sourceConfiguration.timestampFieldRecommendedValue": "推奨値は {defaultValue} です", "xpack.infra.sourceConfiguration.timestampLogColumnDescription": "このシステムフィールドは、{timestampSetting} フィールド設定から判断されたログエントリーの時刻を表示します。", "xpack.infra.sourceConfiguration.unsavedFormPrompt": "終了してよろしいですか?変更内容は失われます", "xpack.infra.sourceErrorPage.failedToLoadDataSourcesMessage": "データソースの読み込みに失敗しました。", @@ -24980,15 +24959,8 @@ "xpack.triggersActionsUI.components.healthCheck.alertsErrorAction": "方法を確認してください。", "xpack.triggersActionsUI.components.healthCheck.alertsErrorTitle": "アラートとアクションを有効にする必要があります", "xpack.triggersActionsUI.components.healthCheck.encryptionErrorAction": "方法を確認してください。", - "xpack.triggersActionsUI.components.healthCheck.encryptionErrorAfterKey": " kibana.ymlファイルで、暗号化された保存されたプラグインが有効になっていることを確認してください。", "xpack.triggersActionsUI.components.healthCheck.encryptionErrorBeforeKey": "ルールを作成するには、値を設定します ", "xpack.triggersActionsUI.components.healthCheck.encryptionErrorTitle": "暗号化された保存されたオブジェクトがありません", - "xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionError": "KibanaとElasticsearchの間でトランスポートレイヤーセキュリティを有効にし、kibana.ymlファイルで暗号化鍵を構成する必要があります。", - "xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionErrorAction": "方法を確認してください。", - "xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionErrorTitle": "追加の設定が必要です", - "xpack.triggersActionsUI.components.healthCheck.tlsError": "アラートはAPIキーに依存し、キーを使用するにはElasticsearchとKibanaの間にTLSが必要です。", - "xpack.triggersActionsUI.components.healthCheck.tlsErrorAction": "TLSを有効にする方法をご覧ください。", - "xpack.triggersActionsUI.components.healthCheck.tlsErrorTitle": "トランスポートレイヤーセキュリティとAPIキーを有効にする必要があります", "xpack.triggersActionsUI.connectors.breadcrumbTitle": "コネクター", "xpack.triggersActionsUI.data.coreQueryParams.aggTypeRequiredErrorMessage": "[aggType]が「{aggType}」のときには[aggField]に値が必要です", "xpack.triggersActionsUI.data.coreQueryParams.dateStartGTdateEndErrorMessage": "[dateStart]が[dateEnd]よりも大です", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 5fbcd26340be3..92646cc1f9cd9 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6844,7 +6844,6 @@ "xpack.apm.transactionActionMenu.viewSampleDocumentLinkLabel": "查看样例文档", "xpack.apm.transactionBreakdown.chartTitle": "跨度类型花费的时间", "xpack.apm.transactionDetails.clearSelectionAriaLabel": "清除所选内容", - "xpack.apm.transactionDetails.distribution.errorTitle": "获取分布时出错", "xpack.apm.transactionDetails.distribution.panelTitle": "延迟分布", "xpack.apm.transactionDetails.emptySelectionText": "单击并拖动以选择范围", "xpack.apm.transactionDetails.errorCount": "{errorCount, number} 个 {errorCount, plural, other {错误}}", @@ -13883,20 +13882,9 @@ "xpack.infra.sourceConfiguration.anomalyThresholdLabel": "最低严重性分数", "xpack.infra.sourceConfiguration.anomalyThresholdTitle": "异常严重性阈值", "xpack.infra.sourceConfiguration.applySettingsButtonLabel": "应用", - "xpack.infra.sourceConfiguration.containerFieldDescription": "用于标识 Docker 容器的字段", - "xpack.infra.sourceConfiguration.containerFieldLabel": "容器 ID", - "xpack.infra.sourceConfiguration.containerFieldRecommendedValue": "推荐值为 {defaultValue}", - "xpack.infra.sourceConfiguration.deprecationMessage": "有关这些字段的配置已过时,将在 8.0.0 中移除。此应用程序专用于 {ecsLink},您应调整索引以使用{documentationLink}。", - "xpack.infra.sourceConfiguration.deprecationNotice": "过时通知", "xpack.infra.sourceConfiguration.discardSettingsButtonLabel": "丢弃", - "xpack.infra.sourceConfiguration.documentedFields": "已记录字段", "xpack.infra.sourceConfiguration.fieldEmptyErrorMessage": "字段不得为空。", "xpack.infra.sourceConfiguration.fieldLogColumnTitle": "字段", - "xpack.infra.sourceConfiguration.fieldsSectionTitle": "字段", - "xpack.infra.sourceConfiguration.hostFieldDescription": "推荐值为 {defaultValue}", - "xpack.infra.sourceConfiguration.hostFieldLabel": "主机名", - "xpack.infra.sourceConfiguration.hostNameFieldDescription": "用于标识主机的字段", - "xpack.infra.sourceConfiguration.hostNameFieldLabel": "主机名", "xpack.infra.sourceConfiguration.indicesSectionTitle": "索引", "xpack.infra.sourceConfiguration.logColumnsSectionTitle": "日志列", "xpack.infra.sourceConfiguration.logIndicesDescription": "用于匹配包含日志数据的索引的索引模式", @@ -13914,17 +13902,8 @@ "xpack.infra.sourceConfiguration.nameSectionTitle": "名称", "xpack.infra.sourceConfiguration.noLogColumnsDescription": "使用上面的按钮将列添加到此列表。", "xpack.infra.sourceConfiguration.noLogColumnsTitle": "无列", - "xpack.infra.sourceConfiguration.podFieldDescription": "用于标识 Kubernetes Pod 的字段", - "xpack.infra.sourceConfiguration.podFieldLabel": "Pod ID", - "xpack.infra.sourceConfiguration.podFieldRecommendedValue": "推荐值为 {defaultValue}", "xpack.infra.sourceConfiguration.removeLogColumnButtonLabel": "删除“{columnDescription}”列", "xpack.infra.sourceConfiguration.systemColumnBadgeLabel": "系统", - "xpack.infra.sourceConfiguration.tiebreakerFieldDescription": "用于时间戳相同的两个条目间决胜的字段", - "xpack.infra.sourceConfiguration.tiebreakerFieldLabel": "决胜属性", - "xpack.infra.sourceConfiguration.tiebreakerFieldRecommendedValue": "推荐值为 {defaultValue}", - "xpack.infra.sourceConfiguration.timestampFieldDescription": "用于排序日志条目的时间戳", - "xpack.infra.sourceConfiguration.timestampFieldLabel": "时间戳", - "xpack.infra.sourceConfiguration.timestampFieldRecommendedValue": "推荐值为 {defaultValue}", "xpack.infra.sourceConfiguration.timestampLogColumnDescription": "此系统字段显示 {timestampSetting} 字段设置所确定的日志条目时间。", "xpack.infra.sourceConfiguration.unsavedFormPrompt": "是否确定要离开?更改将丢失", "xpack.infra.sourceErrorPage.failedToLoadDataSourcesMessage": "无法加载数据源。", @@ -25406,15 +25385,8 @@ "xpack.triggersActionsUI.components.healthCheck.alertsErrorAction": "了解操作方法。", "xpack.triggersActionsUI.components.healthCheck.alertsErrorTitle": "必须启用“告警和操作”", "xpack.triggersActionsUI.components.healthCheck.encryptionErrorAction": "了解操作方法。", - "xpack.triggersActionsUI.components.healthCheck.encryptionErrorAfterKey": " 设置值,并确保启用加密已保存对象插件。", "xpack.triggersActionsUI.components.healthCheck.encryptionErrorBeforeKey": "要创建规则,请在 kibana.yml 文件中为: ", "xpack.triggersActionsUI.components.healthCheck.encryptionErrorTitle": "加密已保存对象不可用", - "xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionError": "必须在 Kibana 和 Elasticsearch 之间启用传输层安全并在 kibana.yml 文件中配置加密密钥。", - "xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionErrorAction": "了解操作方法。", - "xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionErrorTitle": "需要其他设置", - "xpack.triggersActionsUI.components.healthCheck.tlsError": "Alerting 功能依赖于 API 密钥,这需要在 Elasticsearch 与 Kibana 之间启用 TLS。", - "xpack.triggersActionsUI.components.healthCheck.tlsErrorAction": "了解如何启用 TLS。", - "xpack.triggersActionsUI.components.healthCheck.tlsErrorTitle": "必须启用传输层安全和 API 密钥", "xpack.triggersActionsUI.connectors.breadcrumbTitle": "连接器", "xpack.triggersActionsUI.data.coreQueryParams.aggTypeRequiredErrorMessage": "[aggField]:当 [aggType] 为“{aggType}”时必须有值", "xpack.triggersActionsUI.data.coreQueryParams.dateStartGTdateEndErrorMessage": "[dateStart]:晚于 [dateEnd]", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx index ff5992a6542b7..4192ee1a75a8f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx @@ -81,7 +81,7 @@ describe('health check', () => { expect(queryByText('should render')).toBeInTheDocument(); }); - test('renders warning if TLS is required', async () => { + test('renders warning if API keys are disabled', async () => { useKibanaMock().services.http.get = jest.fn().mockImplementation(async () => ({ is_sufficiently_secure: false, has_permanent_encryption_key: true, @@ -103,18 +103,17 @@ describe('health check', () => { // wait for useEffect to run }); - const [description, action] = queryAllByText(/TLS/i); + const [description] = queryAllByText(/API keys/i); + const [action] = queryAllByText(/Learn more/i); expect(description.textContent).toMatchInlineSnapshot( - `"Alerting relies on API keys, which require TLS between Elasticsearch and Kibana. Learn how to enable TLS.(opens in a new tab or window)"` + `"You must enable API keys to use Alerting. Learn more.(opens in a new tab or window)"` ); - expect(action.textContent).toMatchInlineSnapshot( - `"Learn how to enable TLS.(opens in a new tab or window)"` - ); + expect(action.textContent).toMatchInlineSnapshot(`"Learn more.(opens in a new tab or window)"`); expect(action.getAttribute('href')).toMatchInlineSnapshot( - `"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/security-basic-setup.html#encrypt-internode-communication"` + `"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/security-settings.html#api-key-service-settings"` ); }); @@ -142,11 +141,13 @@ describe('health check', () => { const description = queryByRole(/banner/i); expect(description!.textContent).toMatchInlineSnapshot( - `"To create a rule, set a value for xpack.encryptedSavedObjects.encryptionKey in your kibana.yml file and ensure the Encrypted Saved Objects plugin is enabled. Learn how.(opens in a new tab or window)"` + `"You must configure an encryption key to use Alerting. Learn more.(opens in a new tab or window)"` ); const action = queryByText(/Learn/i); - expect(action!.textContent).toMatchInlineSnapshot(`"Learn how.(opens in a new tab or window)"`); + expect(action!.textContent).toMatchInlineSnapshot( + `"Learn more.(opens in a new tab or window)"` + ); expect(action!.getAttribute('href')).toMatchInlineSnapshot( `"https://www.elastic.co/guide/en/kibana/mocked-test-branch/alert-action-settings-kb.html#general-alert-action-settings"` ); @@ -175,14 +176,16 @@ describe('health check', () => { // wait for useEffect to run }); - const description = queryByText(/Transport Layer Security/i); + const description = queryByText(/You must enable/i); expect(description!.textContent).toMatchInlineSnapshot( - `"You must enable Transport Layer Security between Kibana and Elasticsearch and configure an encryption key in your kibana.yml file. Learn how.(opens in a new tab or window)"` + `"You must enable API keys and configure an encryption key to use Alerting. Learn more.(opens in a new tab or window)"` ); const action = queryByText(/Learn/i); - expect(action!.textContent).toMatchInlineSnapshot(`"Learn how.(opens in a new tab or window)"`); + expect(action!.textContent).toMatchInlineSnapshot( + `"Learn more.(opens in a new tab or window)"` + ); expect(action!.getAttribute('href')).toMatchInlineSnapshot( `"https://www.elastic.co/guide/en/kibana/mocked-test-branch/alerting-setup.html#alerting-prerequisites"` ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx index f990e12ed76e5..835c64ee0a05b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx @@ -13,7 +13,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiLink, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { EuiEmptyPrompt, EuiCode } from '@elastic/eui'; +import { EuiEmptyPrompt } from '@elastic/eui'; import { DocLinksStart } from 'kibana/public'; import './health_check.scss'; import { useHealthContext } from '../context/health_context'; @@ -82,11 +82,11 @@ export const HealthCheck: React.FunctionComponent = ({ ) : !healthCheck.isAlertsAvailable ? ( ) : !healthCheck.isSufficientlySecure && !healthCheck.hasPermanentEncryptionKey ? ( - + ) : !healthCheck.hasPermanentEncryptionKey ? ( ) : ( - + ); } ) @@ -108,7 +108,7 @@ const EncryptionError = ({ docLinks, className }: PromptErrorProps) => (

} @@ -118,22 +118,14 @@ const EncryptionError = ({ docLinks, className }: PromptErrorProps) => ( {i18n.translate( 'xpack.triggersActionsUI.components.healthCheck.encryptionErrorBeforeKey', { - defaultMessage: 'To create a rule, set a value for ', - } - )} - {'xpack.encryptedSavedObjects.encryptionKey'} - {i18n.translate( - 'xpack.triggersActionsUI.components.healthCheck.encryptionErrorAfterKey', - { - defaultMessage: - ' in your kibana.yml file and ensure the Encrypted Saved Objects plugin is enabled. ', + defaultMessage: 'You must configure an encryption key to use Alerting. ', } )} {i18n.translate( 'xpack.triggersActionsUI.components.healthCheck.encryptionErrorAction', { - defaultMessage: 'Learn how.', + defaultMessage: 'Learn more.', } )} @@ -143,7 +135,7 @@ const EncryptionError = ({ docLinks, className }: PromptErrorProps) => ( /> ); -const TlsError = ({ docLinks, className }: PromptErrorProps) => ( +const ApiKeysDisabledError = ({ docLinks, className }: PromptErrorProps) => ( ( title={

} body={

- {i18n.translate('xpack.triggersActionsUI.components.healthCheck.tlsError', { - defaultMessage: - 'Alerting relies on API keys, which require TLS between Elasticsearch and Kibana. ', + {i18n.translate('xpack.triggersActionsUI.components.healthCheck.apiKeysDisabledError', { + defaultMessage: 'You must enable API keys to use Alerting. ', })} - - {i18n.translate('xpack.triggersActionsUI.components.healthCheck.tlsErrorAction', { - defaultMessage: 'Learn how to enable TLS.', - })} + + {i18n.translate( + 'xpack.triggersActionsUI.components.healthCheck.apiKeysDisabledErrorAction', + { + defaultMessage: 'Learn more.', + } + )}

@@ -206,7 +204,7 @@ const AlertsError = ({ docLinks, className }: PromptErrorProps) => ( /> ); -const TlsAndEncryptionError = ({ docLinks, className }: PromptErrorProps) => ( +const ApiKeysAndEncryptionError = ({ docLinks, className }: PromptErrorProps) => ( ( title={

@@ -223,15 +221,18 @@ const TlsAndEncryptionError = ({ docLinks, className }: PromptErrorProps) => ( body={

- {i18n.translate('xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionError', { - defaultMessage: - 'You must enable Transport Layer Security between Kibana and Elasticsearch and configure an encryption key in your kibana.yml file. ', - })} + {i18n.translate( + 'xpack.triggersActionsUI.components.healthCheck.apiKeysAndEncryptionError', + { + defaultMessage: + 'You must enable API keys and configure an encryption key to use Alerting. ', + } + )} {i18n.translate( - 'xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionErrorAction', + 'xpack.triggersActionsUI.components.healthCheck.apiKeysAndEncryptionErrorAction', { - defaultMessage: 'Learn how.', + defaultMessage: 'Learn more.', } )} 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 e7948f4ad532c..7b181ac2cf50c 100644 --- a/x-pack/plugins/uptime/common/runtime_types/ping/synthetics.ts +++ b/x-pack/plugins/uptime/common/runtime_types/ping/synthetics.ts @@ -22,6 +22,10 @@ export const JourneyStepType = t.intersection([ name: t.string, status: t.string, type: t.string, + timespan: t.type({ + gte: t.string, + lt: t.string, + }), }), synthetics: t.partial({ error: t.partial({ diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx index 777491c503fd9..1e493ad21b4d3 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx @@ -45,7 +45,11 @@ export const StepDetailContainer: React.FC = ({ checkGroup, stepIndex }) )} {journey && activeStep && !journey.loading && ( - + )} ); diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.tsx index d249c23c44d75..95da0ea0a45f6 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.tsx @@ -15,6 +15,7 @@ import { networkEventsSelector } from '../../../../../state/selectors'; import { WaterfallChartWrapper } from './waterfall_chart_wrapper'; import { extractItems } from './data_formatting'; import { useStepWaterfallMetrics } from '../use_step_waterfall_metrics'; +import { JourneyStep } from '../../../../../../common/runtime_types'; export const NO_DATA_TEXT = i18n.translate('xpack.uptime.synthetics.stepDetail.waterfallNoData', { defaultMessage: 'No waterfall data could be found for this step', @@ -22,10 +23,11 @@ export const NO_DATA_TEXT = i18n.translate('xpack.uptime.synthetics.stepDetail.w interface Props { checkGroup: string; + activeStep?: JourneyStep; stepIndex: number; } -export const WaterfallChartContainer: React.FC = ({ checkGroup, stepIndex }) => { +export const WaterfallChartContainer: React.FC = ({ checkGroup, stepIndex, activeStep }) => { const dispatch = useDispatch(); useEffect(() => { @@ -79,6 +81,7 @@ export const WaterfallChartContainer: React.FC = ({ checkGroup, stepIndex data={extractItems(networkEvents.events)} markerItems={metrics} total={networkEvents.total} + activeStep={activeStep} /> )} {waterfallLoaded && hasEvents && !isWaterfallSupported && ( diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.tsx index 8071fd1e3c4d3..aaa3d5c1cc23c 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.tsx @@ -15,6 +15,7 @@ import { WaterfallFilter } from './waterfall_filter'; import { WaterfallFlyout } from './waterfall_flyout'; import { WaterfallSidebarItem } from './waterfall_sidebar_item'; import { MarkerItems } from '../../waterfall/context/waterfall_chart'; +import { JourneyStep } from '../../../../../../common/runtime_types'; export const renderLegendItem: RenderItem = (item) => { return ( @@ -26,11 +27,17 @@ export const renderLegendItem: RenderItem = (item) => { interface Props { total: number; + activeStep?: JourneyStep; data: NetworkItems; markerItems?: MarkerItems; } -export const WaterfallChartWrapper: React.FC = ({ data, total, markerItems }) => { +export const WaterfallChartWrapper: React.FC = ({ + data, + total, + markerItems, + activeStep, +}) => { const [query, setQuery] = useState(''); const [activeFilters, setActiveFilters] = useState([]); const [onlyHighlighted, setOnlyHighlighted] = useState(false); @@ -109,6 +116,7 @@ export const WaterfallChartWrapper: React.FC = ({ data, total, markerItem return ( ; + } + + return ( + setIsOpen(false)} + anchorPosition="downLeft" + panelStyle={{ paddingBottom: 0, paddingLeft: 4 }} + zIndex={100} + button={ + setIsOpen((prevState) => !prevState)} + /> + } + > + + + ); +} diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_marker_trend.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_marker_trend.tsx new file mode 100644 index 0000000000000..6ff7835633914 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_marker_trend.tsx @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; + +import { EuiButton } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import moment from 'moment'; +import { useUptimeStartPlugins } from '../../../../../contexts/uptime_startup_plugins_context'; +import { useUptimeSettingsContext } from '../../../../../contexts/uptime_settings_context'; +import { AllSeries, createExploratoryViewUrl } from '../../../../../../../observability/public'; +import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; +import { useWaterfallContext } from '../context/waterfall_chart'; +import { JourneyStep } from '../../../../../../common/runtime_types'; + +const getLast48Intervals = (activeStep: JourneyStep) => { + const { lt, gte } = activeStep.monitor.timespan!; + const inDays = moment(lt).diff(moment(gte), 'days'); + if (inDays > 0) { + return { to: 'now', from: `now-${inDays * 48}d` }; + } + + const inHours = moment(lt).diff(moment(gte), 'hours'); + if (inHours > 0) { + return { to: 'now', from: `now-${inHours * 48}h` }; + } + + const inMinutes = moment(lt).diff(moment(gte), 'minutes'); + if (inMinutes > 0) { + return { to: 'now', from: `now-${inMinutes * 48}m` }; + } + + const inSeconds = moment(lt).diff(moment(gte), 'seconds'); + return { to: 'now', from: `now-${inSeconds * 48}s` }; +}; + +export function WaterfallMarkerTrend({ title, field }: { title: string; field: string }) { + const { observability } = useUptimeStartPlugins(); + + const EmbeddableExpVIew = observability!.ExploratoryViewEmbeddable; + + const { basePath } = useUptimeSettingsContext(); + + const { activeStep } = useWaterfallContext(); + + if (!activeStep) { + return null; + } + + const allSeries: AllSeries = [ + { + name: `${title}(${activeStep.synthetics.step?.name!})`, + selectedMetricField: field, + time: getLast48Intervals(activeStep), + seriesType: 'area', + dataType: 'synthetics', + reportDefinitions: { + 'monitor.name': [activeStep.monitor.name!], + 'synthetics.step.name.keyword': [activeStep.synthetics.step?.name!], + }, + operationType: 'last_value', + }, + ]; + + const href = createExploratoryViewUrl( + { + reportType: 'kpi-over-time', + allSeries, + }, + basePath + ); + + return ( + + + {EXPLORE_LABEL} + + } + reportType={'kpi-over-time'} + attributes={allSeries} + axisTitlesVisibility={{ x: false, yLeft: false, yRight: false }} + legendIsVisible={false} + /> + + ); +} + +export const EXPLORE_LABEL = i18n.translate('xpack.uptime.synthetics.markers.explore', { + defaultMessage: 'Explore', +}); + +const Wrapper = euiStyled.div` + height: 200px; + width: 400px; + &&& { + .expExpressionRenderer__expression { + padding-bottom: 0 !important; + } + } +`; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_markers.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_markers.tsx index b341b052e0102..d8f6468015ede 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_markers.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_markers.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { AnnotationDomainType, LineAnnotation } from '@elastic/charts'; -import { EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useWaterfallContext } from '..'; import { useTheme } from '../../../../../../../observability/public'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; +import { WaterfallMarkerIcon } from './waterfall_marker_icon'; export const FCP_LABEL = i18n.translate('xpack.uptime.synthetics.waterfall.fcpLabel', { defaultMessage: 'First contentful paint', @@ -39,6 +39,12 @@ export const DOCUMENT_CONTENT_LOADED_LABEL = i18n.translate( } ); +export const SYNTHETICS_CLS = 'browser.experience.cls'; +export const SYNTHETICS_LCP = 'browser.experience.lcp.us'; +export const SYNTHETICS_FCP = 'browser.experience.fcp.us'; +export const SYNTHETICS_DOCUMENT_ONLOAD = 'browser.experience.load.us'; +export const SYNTHETICS_DCL = 'browser.experience.dcl.us'; + export function WaterfallChartMarkers() { const { markerItems } = useWaterfallContext(); @@ -48,12 +54,32 @@ export function WaterfallChartMarkers() { return null; } - const markersInfo: Record = { - domContentLoaded: { label: DOCUMENT_CONTENT_LOADED_LABEL, color: theme.eui.euiColorVis0 }, - firstContentfulPaint: { label: FCP_LABEL, color: theme.eui.euiColorVis1 }, - largestContentfulPaint: { label: LCP_LABEL, color: theme.eui.euiColorVis2 }, - layoutShift: { label: LAYOUT_SHIFT_LABEL, color: theme.eui.euiColorVis3 }, - loadEvent: { label: LOAD_EVENT_LABEL, color: theme.eui.euiColorVis9 }, + const markersInfo: Record = { + domContentLoaded: { + label: DOCUMENT_CONTENT_LOADED_LABEL, + color: theme.eui.euiColorVis0, + field: SYNTHETICS_DCL, + }, + firstContentfulPaint: { + label: FCP_LABEL, + color: theme.eui.euiColorVis1, + field: SYNTHETICS_FCP, + }, + largestContentfulPaint: { + label: LCP_LABEL, + color: theme.eui.euiColorVis2, + field: SYNTHETICS_LCP, + }, + layoutShift: { + label: LAYOUT_SHIFT_LABEL, + color: theme.eui.euiColorVis3, + field: SYNTHETICS_CLS, + }, + loadEvent: { + label: LOAD_EVENT_LABEL, + color: theme.eui.euiColorVis9, + field: SYNTHETICS_DOCUMENT_ONLOAD, + }, }; return ( @@ -73,7 +99,9 @@ export function WaterfallChartMarkers() { }), }, ]} - marker={} + marker={ + + } style={{ line: { strokeWidth: 2, diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx index cce0533293e07..d495b7432bce7 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx @@ -9,6 +9,7 @@ import React, { createContext, useContext, Context } from 'react'; import { WaterfallData, WaterfallDataEntry, WaterfallMetadata } from '../types'; import { OnSidebarClick, OnElementClick, OnProjectionClick } from '../components/use_flyout'; import { SidebarItems } from '../../step_detail/waterfall/types'; +import { JourneyStep } from '../../../../../../common/runtime_types'; export type MarkerItems = Array<{ id: @@ -38,6 +39,7 @@ export interface IWaterfallContext { index?: number ) => JSX.Element; markerItems?: MarkerItems; + activeStep?: JourneyStep; } export const WaterfallContext = createContext>({}); @@ -56,6 +58,7 @@ interface ProviderProps { metadata: IWaterfallContext['metadata']; renderTooltipItem: IWaterfallContext['renderTooltipItem']; markerItems?: MarkerItems; + activeStep?: JourneyStep; } export const WaterfallProvider: React.FC = ({ @@ -73,11 +76,13 @@ export const WaterfallProvider: React.FC = ({ totalNetworkRequests, highlightedNetworkRequests, fetchedNetworkRequests, + activeStep, }) => { return ( >({}); @@ -14,3 +14,5 @@ export const UptimeStartupPluginsContextProvider: React.FC ; + +export const useUptimeStartPlugins = () => useContext(UptimeStartupPluginsContext); diff --git a/x-pack/test/accessibility/apps/lens.ts b/x-pack/test/accessibility/apps/lens.ts index ff769ddd29bfc..b8ddd774741b6 100644 --- a/x-pack/test/accessibility/apps/lens.ts +++ b/x-pack/test/accessibility/apps/lens.ts @@ -14,7 +14,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const listingTable = getService('listingTable'); - describe('Lens', () => { + // 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'); diff --git a/x-pack/test/apm_api_integration/tests/transactions/latency_overall_distribution.ts b/x-pack/test/apm_api_integration/tests/transactions/latency_overall_distribution.ts index c915ac8911e37..b204f939020d7 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/latency_overall_distribution.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/latency_overall_distribution.ts @@ -12,17 +12,17 @@ import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const apmApiClient = getService('apmApiClient'); - const endpoint = 'GET /internal/apm/latency/overall_distribution'; + const endpoint = 'POST /internal/apm/latency/overall_distribution'; // This matches the parameters used for the other tab's search strategy approach in `../correlations/*`. const getOptions = () => ({ params: { - query: { + body: { environment: 'ENVIRONMENT_ALL', start: '2020', end: '2021', kuery: '', - percentileThreshold: '95', + percentileThreshold: 95, }, }, });